0027607: Visualization - Implement adaptive screen space sampling in path tracing
[occt.git] / src / Shaders / RaytraceBase.fs
index 1edc7a2..6c5f5e4 100644 (file)
@@ -1,6 +1,20 @@
+#ifdef ADAPTIVE_SAMPLING
+  #extension GL_ARB_shader_image_load_store : require
+  #extension GL_NV_shader_atomic_float : require
+#endif
+
+#ifdef USE_TEXTURES
+  #extension GL_ARB_bindless_texture : require
+#endif
+
 //! Normalized pixel coordinates.
 in vec2 vPixel;
 
+//! Sub-pixel offset in X direction for FSAA.
+uniform float uOffsetX = 0.f;
+//! Sub-pixel offset in Y direction for FSAA.
+uniform float uOffsetY = 0.f;
+
 //! Origin of viewing ray in left-top corner.
 uniform vec3 uOriginLT;
 //! Origin of viewing ray in left-bottom corner.
@@ -10,6 +24,11 @@ uniform vec3 uOriginRT;
 //! Origin of viewing ray in right-bottom corner.
 uniform vec3 uOriginRB;
 
+//! Width of the rendering window.
+uniform int uWinSizeX;
+//! Height of the rendering window.
+uniform int uWinSizeY;
+
 //! Direction of viewing ray in left-top corner.
 uniform vec3 uDirectLT;
 //! Direction of viewing ray in left-bottom corner.
@@ -19,24 +38,29 @@ uniform vec3 uDirectRT;
 //! Direction of viewing ray in right-bottom corner.
 uniform vec3 uDirectRB;
 
-//! Texture buffer of data records of high-level BVH nodes.
-uniform isamplerBuffer uSceneNodeInfoTexture;
-//! Texture buffer of minimum points of high-level BVH nodes.
-uniform samplerBuffer uSceneMinPointTexture;
-//! Texture buffer of maximum points of high-level BVH nodes.
-uniform samplerBuffer uSceneMaxPointTexture;
+//! Inverse model-view-projection matrix.
+uniform mat4 uUnviewMat;
+
+//! Model-view-projection matrix.
+uniform mat4 uViewMat;
 
 //! Texture buffer of data records of bottom-level BVH nodes.
-uniform isamplerBuffer uObjectNodeInfoTexture;
+uniform isamplerBuffer uSceneNodeInfoTexture;
 //! Texture buffer of minimum points of bottom-level BVH nodes.
-uniform samplerBuffer uObjectMinPointTexture;
+uniform samplerBuffer uSceneMinPointTexture;
 //! Texture buffer of maximum points of bottom-level BVH nodes.
-uniform samplerBuffer uObjectMaxPointTexture;
+uniform samplerBuffer uSceneMaxPointTexture;
+//! Texture buffer of transformations of high-level BVH nodes.
+uniform samplerBuffer uSceneTransformTexture;
 
 //! Texture buffer of vertex coords.
 uniform samplerBuffer uGeometryVertexTexture;
 //! Texture buffer of vertex normals.
 uniform samplerBuffer uGeometryNormalTexture;
+#ifdef USE_TEXTURES
+  //! Texture buffer of per-vertex UV-coordinates.
+  uniform samplerBuffer uGeometryTexCrdTexture;
+#endif
 //! Texture buffer of triangle indices.
 uniform isamplerBuffer uGeometryTriangTexture;
 
@@ -52,26 +76,46 @@ uniform int uLightCount;
 //! Intensity of global ambient light.
 uniform vec4 uGlobalAmbient;
 
-//! Enables/disables environment map.
-uniform int uEnvironmentEnable;
-//! Enables/disables computation of shadows.
-uniform int uShadowsEnable;
-//! Enables/disables computation of reflections.
-uniform int uReflectionsEnable;
+//! Enables/disables hard shadows.
+uniform int uShadowsEnabled;
+//! Enables/disables specular reflections.
+uniform int uReflectEnabled;
+//! Enables/disables spherical environment map.
+uniform int uSphereMapEnabled;
+//! Enables/disables environment map background.
+uniform int uSphereMapForBack;
 
 //! Radius of bounding sphere of the scene.
 uniform float uSceneRadius;
 //! Scene epsilon to prevent self-intersections.
 uniform float uSceneEpsilon;
 
+#ifdef USE_TEXTURES
+  //! Unique 64-bit handles of OpenGL textures.
+  uniform uvec2 uTextureSamplers[MAX_TEX_NUMBER];
+#endif
+
+#ifdef ADAPTIVE_SAMPLING
+  //! OpenGL image used for accumulating rendering result.
+  volatile restrict layout(size1x32) uniform image2D  uRenderImage;
+
+  //! OpenGL image storing offsets of sampled pixels blocks.
+  coherent restrict layout(size2x32) uniform iimage2D uOffsetImage;
+#endif
+
+//! Top color of gradient background.
+uniform vec4 uBackColorTop = vec4 (0.0);
+//! Bottom color of gradient background.
+uniform vec4 uBackColorBot = vec4 (0.0);
+
 /////////////////////////////////////////////////////////////////////////////////////////
 // Specific data types
-  
+
 //! Stores ray parameters.
 struct SRay
 {
   vec3 Origin;
-  
+
   vec3 Direct;
 };
 
@@ -79,9 +123,9 @@ struct SRay
 struct SIntersect
 {
   float Time;
-  
+
   vec2 UV;
-  
+
   vec3 Normal;
 };
 
@@ -90,14 +134,137 @@ struct SIntersect
 
 #define MAXFLOAT 1e15f
 
-#define SMALL vec3 (exp2 (-80.f))
+#define SMALL vec3 (exp2 (-80.0f))
+
+#define ZERO vec3 (0.0f, 0.0f, 0.0f)
+#define UNIT vec3 (1.0f, 1.0f, 1.0f)
+
+#define AXIS_X vec3 (1.0f, 0.0f, 0.0f)
+#define AXIS_Y vec3 (0.0f, 1.0f, 0.0f)
+#define AXIS_Z vec3 (0.0f, 0.0f, 1.0f)
+
+#define M_PI 3.14159265f
+
+#define LUMA vec3 (0.2126f, 0.7152f, 0.0722f)
+
+// =======================================================================
+// function : MatrixRowMultiplyDir
+// purpose  : Multiplies a vector by matrix
+// =======================================================================
+vec3 MatrixRowMultiplyDir (in vec3 v,
+                           in vec4 m0,
+                           in vec4 m1,
+                           in vec4 m2)
+{
+  return vec3 (dot (m0.xyz, v),
+               dot (m1.xyz, v),
+               dot (m2.xyz, v));
+}
+
+//! 32-bit state of random number generator.
+uint RandState;
+
+// =======================================================================
+// function : SeedRand
+// purpose  : Applies hash function by Thomas Wang to randomize seeds
+//            (see http://www.burtleburtle.net/bob/hash/integer.html)
+// =======================================================================
+void SeedRand (in int theSeed, in int theSizeX, in int theRadius)
+{
+  RandState = uint (int (gl_FragCoord.y) / theRadius * theSizeX + int (gl_FragCoord.x) / theRadius + theSeed);
+
+  RandState = (RandState + 0x479ab41du) + (RandState <<  8);
+  RandState = (RandState ^ 0xe4aa10ceu) ^ (RandState >>  5);
+  RandState = (RandState + 0x9942f0a6u) - (RandState << 14);
+  RandState = (RandState ^ 0x5aedd67du) ^ (RandState >>  3);
+  RandState = (RandState + 0x17bea992u) + (RandState <<  7);
+}
+
+// =======================================================================
+// function : RandInt
+// purpose  : Generates integer using Xorshift algorithm by G. Marsaglia
+// =======================================================================
+uint RandInt()
+{
+  RandState ^= (RandState << 13);
+  RandState ^= (RandState >> 17);
+  RandState ^= (RandState <<  5);
+
+  return RandState;
+}
+
+// =======================================================================
+// function : RandFloat
+// purpose  : Generates a random float in [0, 1) range
+// =======================================================================
+float RandFloat()
+{
+  return float (RandInt()) * (1.f / 4294967296.f);
+}
+
+// =======================================================================
+// function : MatrixColMultiplyPnt
+// purpose  : Multiplies a vector by matrix
+// =======================================================================
+vec3 MatrixColMultiplyPnt (in vec3 v,
+                           in vec4 m0,
+                           in vec4 m1,
+                           in vec4 m2,
+                           in vec4 m3)
+{
+  return vec3 (m0.x * v.x + m1.x * v.y + m2.x * v.z + m3.x,
+               m0.y * v.x + m1.y * v.y + m2.y * v.z + m3.y,
+               m0.z * v.x + m1.z * v.y + m2.z * v.z + m3.z);
+}
+
+// =======================================================================
+// function : MatrixColMultiplyDir
+// purpose  : Multiplies a vector by matrix
+// =======================================================================
+vec3 MatrixColMultiplyDir (in vec3 v,
+                           in vec4 m0,
+                           in vec4 m1,
+                           in vec4 m2)
+{
+  return vec3 (m0.x * v.x + m1.x * v.y + m2.x * v.z,
+               m0.y * v.x + m1.y * v.y + m2.y * v.z,
+               m0.z * v.x + m1.z * v.y + m2.z * v.z);
+}
+
+//=======================================================================
+// function : InverseDirection
+// purpose  : Returns safely inverted direction of the given one
+//=======================================================================
+vec3 InverseDirection (in vec3 theInput)
+{
+  vec3 anInverse = 1.f / max (abs (theInput), SMALL);
+
+  return mix (-anInverse, anInverse, step (ZERO, theInput));
+}
 
-#define ZERO vec3 (0.f, 0.f, 0.f)
-#define UNIT vec3 (1.f, 1.f, 1.f)
+//=======================================================================
+// function : BackgroundColor
+// purpose  : Returns color of gradient background
+//=======================================================================
+vec4 BackgroundColor()
+{
+#ifdef ADAPTIVE_SAMPLING
+
+  ivec2 aFragCoord = ivec2 (gl_FragCoord.xy);
+
+  ivec2 aTileXY = imageLoad (uOffsetImage, ivec2 (aFragCoord.x / BLOCK_SIZE,
+                                                  aFragCoord.y / BLOCK_SIZE)).xy;
+
+  aTileXY.y += aFragCoord.y % min (uWinSizeY - aTileXY.y, BLOCK_SIZE);
+
+  return mix (uBackColorBot, uBackColorTop, float (aTileXY.y) / uWinSizeY);
 
-#define AXIS_X vec3 (1.f, 0.f, 0.f)
-#define AXIS_Y vec3 (0.f, 1.f, 0.f)
-#define AXIS_Z vec3 (0.f, 0.f, 1.f)
+#else
+
+  return mix (uBackColorBot, uBackColorTop, vPixel.y);
+
+#endif
+}
 
 /////////////////////////////////////////////////////////////////////////////////////////
 // Functions for compute ray-object intersection
@@ -113,9 +280,10 @@ SRay GenerateRay (in vec2 thePixel)
 
   vec3 aD0 = mix (uDirectLB, uDirectRB, thePixel.x);
   vec3 aD1 = mix (uDirectLT, uDirectRT, thePixel.x);
-  
-  return SRay (mix (aP0, aP1, thePixel.y),
-               mix (aD0, aD1, thePixel.y));
+
+  vec3 aDirection = normalize (mix (aD0, aD1, thePixel.y));
+
+  return SRay (mix (aP0, aP1, thePixel.y), aDirection);
 }
 
 // =======================================================================
@@ -127,16 +295,16 @@ float IntersectSphere (in SRay theRay, in float theRadius)
   float aDdotD = dot (theRay.Direct, theRay.Direct);
   float aDdotO = dot (theRay.Direct, theRay.Origin);
   float aOdotO = dot (theRay.Origin, theRay.Origin);
-  
+
   float aD = aDdotO * aDdotO - aDdotD * (aOdotO - theRadius * theRadius);
-  
-  if (aD > 0.f)
+
+  if (aD > 0.0f)
   {
-    float aTime = (sqrt (aD) - aDdotO) * (1.f / aDdotD);
+    float aTime = (sqrt (aD) - aDdotO) * (1.0f / aDdotD);
     
-    return aTime > 0.f ? aTime : MAXFLOAT;
+    return aTime > 0.0f ? aTime : MAXFLOAT;
   }
-  
+
   return MAXFLOAT;
 }
 
@@ -144,141 +312,265 @@ float IntersectSphere (in SRay theRay, in float theRadius)
 // function : IntersectTriangle
 // purpose  : Computes ray-triangle intersection (branchless version)
 // =======================================================================
-float IntersectTriangle (in SRay theRay,
-                         in vec3 thePnt0,
-                         in vec3 thePnt1,
-                         in vec3 thePnt2,
-                         out vec2 theUV,
-                         out vec3 theNorm)
+void IntersectTriangle (in SRay theRay,
+                        in vec3 thePnt0,
+                        in vec3 thePnt1,
+                        in vec3 thePnt2,
+                        out vec3 theUVT,
+                        out vec3 theNorm)
 {
+  vec3 aToTrg = thePnt0 - theRay.Origin;
+
   vec3 aEdge0 = thePnt1 - thePnt0;
   vec3 aEdge1 = thePnt0 - thePnt2;
-  
+
   theNorm = cross (aEdge1, aEdge0);
 
-  vec3 aEdge2 = (1.f / dot (theNorm, theRay.Direct)) * (thePnt0 - theRay.Origin);
-  
-  float aTime = dot (theNorm, aEdge2);
-
-  vec3 theVec = cross (theRay.Direct, aEdge2);
-  
-  theUV.x = dot (theVec, aEdge1);
-  theUV.y = dot (theVec, aEdge0);
-  
-  return bool (int(aTime >= 0.f) &
-               int(theUV.x >= 0.f) &
-               int(theUV.y >= 0.f) &
-               int(theUV.x + theUV.y <= 1.f)) ? aTime : MAXFLOAT;
+  vec3 theVect = cross (theRay.Direct, aToTrg);
+
+  theUVT = vec3 (dot (theNorm, aToTrg),
+                 dot (theVect, aEdge1),
+                 dot (theVect, aEdge0)) * (1.f / dot (theNorm, theRay.Direct));
+
+  theUVT.x = any (lessThan (theUVT, ZERO)) || (theUVT.y + theUVT.z) > 1.f ? MAXFLOAT : theUVT.x;
 }
 
-//! Global stack shared between traversal functions.
-int Stack[STACK_SIZE];
+#define EMPTY_ROOT ivec4(0)
+
+//! Utility structure containing information about
+//! currently traversing sub-tree of scene's BVH.
+struct SSubTree
+{
+  //! Transformed ray.
+  SRay  TrsfRay;
+
+  //! Inversed ray direction.
+  vec3  Inverse;
+
+  //! Parameters of sub-root node.
+  ivec4 SubData;
+};
+
+#define MATERIAL_AMBN(index) (18 * index + 0)
+#define MATERIAL_DIFF(index) (18 * index + 1)
+#define MATERIAL_SPEC(index) (18 * index + 2)
+#define MATERIAL_EMIS(index) (18 * index + 3)
+#define MATERIAL_REFL(index) (18 * index + 4)
+#define MATERIAL_REFR(index) (18 * index + 5)
+#define MATERIAL_TRAN(index) (18 * index + 6)
+#define MATERIAL_TRS1(index) (18 * index + 7)
+#define MATERIAL_TRS2(index) (18 * index + 8)
+#define MATERIAL_TRS3(index) (18 * index + 9)
+
+#define TRS_OFFSET(treelet) treelet.SubData.x
+#define BVH_OFFSET(treelet) treelet.SubData.y
+#define VRT_OFFSET(treelet) treelet.SubData.z
+#define TRG_OFFSET(treelet) treelet.SubData.w
 
 //! Identifies the absence of intersection.
 #define INALID_HIT ivec4 (-1)
 
+//! Global stack shared between traversal functions.
+int Stack[STACK_SIZE];
+
 // =======================================================================
-// function : ObjectNearestHit
-// purpose  : Finds intersection with nearest object triangle
+// function : pop
+// purpose  :
 // =======================================================================
-ivec4 ObjectNearestHit (in int theBVHOffset, in int theVrtOffset, in int theTrgOffset,
-  in SRay theRay, in vec3 theInverse, inout SIntersect theHit, in int theSentinel)
+int pop (inout int theHead)
 {
-  int aHead = theSentinel; // stack pointer
-  int aNode = 0;           // node to visit
+  int aData = Stack[theHead];
+
+  int aMask = aData >> 26;
+  int aNode = aMask & 0x3;
 
+  aMask >>= 2;
+
+  if ((aMask & 0x3) == aNode)
+  {
+    --theHead;
+  }
+  else
+  {
+    aMask |= (aMask << 2) & 0x30;
+
+    Stack[theHead] = (aData & 0x03FFFFFF) | (aMask << 26);
+  }
+
+  return (aData & 0x03FFFFFF) + aNode;
+}
+
+// =======================================================================
+// function : SceneNearestHit
+// purpose  : Finds intersection with nearest scene triangle
+// =======================================================================
+ivec4 SceneNearestHit (in SRay theRay, in vec3 theInverse, inout SIntersect theHit, out int theTrsfId)
+{
   ivec4 aTriIndex = INALID_HIT;
 
-  float aTimeOut;
-  float aTimeLft;
-  float aTimeRgh;
+  int aNode =  0; // node to traverse
+  int aHead = -1; // pointer of stack
+  int aStop = -1; // BVH level switch
+
+  SSubTree aSubTree = SSubTree (theRay, theInverse, EMPTY_ROOT);
 
-  while (true)
+  for (bool toContinue = true; toContinue; /* none */)
   {
-    ivec3 aData = texelFetch (uObjectNodeInfoTexture, aNode + theBVHOffset).xyz;
+    ivec4 aData = texelFetch (uSceneNodeInfoTexture, aNode);
 
     if (aData.x == 0) // if inner node
     {
-      vec3 aNodeMinLft = texelFetch (uObjectMinPointTexture, aData.y + theBVHOffset).xyz;
-      vec3 aNodeMaxLft = texelFetch (uObjectMaxPointTexture, aData.y + theBVHOffset).xyz;
-      vec3 aNodeMinRgh = texelFetch (uObjectMinPointTexture, aData.z + theBVHOffset).xyz;
-      vec3 aNodeMaxRgh = texelFetch (uObjectMaxPointTexture, aData.z + theBVHOffset).xyz;
+      aData.y += BVH_OFFSET (aSubTree);
+
+      vec4 aHitTimes = vec4 (MAXFLOAT,
+                             MAXFLOAT,
+                             MAXFLOAT,
+                             MAXFLOAT);
+
+      vec3 aRayOriginInverse = -aSubTree.TrsfRay.Origin * aSubTree.Inverse;
+
+      vec3 aNodeMin0 = texelFetch (uSceneMinPointTexture, aData.y +                0).xyz * aSubTree.Inverse + aRayOriginInverse;
+      vec3 aNodeMin1 = texelFetch (uSceneMinPointTexture, aData.y +                1).xyz * aSubTree.Inverse + aRayOriginInverse;
+      vec3 aNodeMin2 = texelFetch (uSceneMinPointTexture, aData.y + min (2, aData.z)).xyz * aSubTree.Inverse + aRayOriginInverse;
+      vec3 aNodeMin3 = texelFetch (uSceneMinPointTexture, aData.y + min (3, aData.z)).xyz * aSubTree.Inverse + aRayOriginInverse;
+      vec3 aNodeMax0 = texelFetch (uSceneMaxPointTexture, aData.y +                0).xyz * aSubTree.Inverse + aRayOriginInverse;
+      vec3 aNodeMax1 = texelFetch (uSceneMaxPointTexture, aData.y +                1).xyz * aSubTree.Inverse + aRayOriginInverse;
+      vec3 aNodeMax2 = texelFetch (uSceneMaxPointTexture, aData.y + min (2, aData.z)).xyz * aSubTree.Inverse + aRayOriginInverse;
+      vec3 aNodeMax3 = texelFetch (uSceneMaxPointTexture, aData.y + min (3, aData.z)).xyz * aSubTree.Inverse + aRayOriginInverse;
+
+      vec3 aTimeMax = max (aNodeMin0, aNodeMax0);
+      vec3 aTimeMin = min (aNodeMin0, aNodeMax0);
 
-      vec3 aTime0 = (aNodeMinLft - theRay.Origin) * theInverse;
-      vec3 aTime1 = (aNodeMaxLft - theRay.Origin) * theInverse;
-      
-      vec3 aTimeMax = max (aTime0, aTime1);
-      vec3 aTimeMin = min (aTime0, aTime1);
+      float aTimeLeave = min (aTimeMax.x, min (aTimeMax.y, aTimeMax.z));
+      float aTimeEnter = max (aTimeMin.x, max (aTimeMin.y, aTimeMin.z));
 
-      aTime0 = (aNodeMinRgh - theRay.Origin) * theInverse;
-      aTime1 = (aNodeMaxRgh - theRay.Origin) * theInverse;
-      
-      aTimeOut = min (aTimeMax.x, min (aTimeMax.y, aTimeMax.z));
-      aTimeLft = max (aTimeMin.x, max (aTimeMin.y, aTimeMin.z));
+      aHitTimes.x = mix (MAXFLOAT, aTimeEnter,
+        aTimeEnter <= aTimeLeave && aTimeEnter <= theHit.Time && aTimeLeave >= 0.f);
 
-      int aHitLft = int(aTimeLft <= aTimeOut) & int(aTimeOut >= 0.f) & int(aTimeLft <= theHit.Time);
+      aTimeMax = max (aNodeMin1, aNodeMax1);
+      aTimeMin = min (aNodeMin1, aNodeMax1);
 
-      aTimeMax = max (aTime0, aTime1);
-      aTimeMin = min (aTime0, aTime1);
+      aTimeLeave = min (aTimeMax.x, min (aTimeMax.y, aTimeMax.z));
+      aTimeEnter = max (aTimeMin.x, max (aTimeMin.y, aTimeMin.z));
 
-      aTimeOut = min (aTimeMax.x, min (aTimeMax.y, aTimeMax.z));
-      aTimeRgh = max (aTimeMin.x, max (aTimeMin.y, aTimeMin.z));
+      aHitTimes.y = mix (MAXFLOAT, aTimeEnter,
+        aTimeEnter <= aTimeLeave && aTimeEnter <= theHit.Time && aTimeLeave >= 0.f);
 
-      int aHitRgh = int(aTimeRgh <= aTimeOut) & int(aTimeOut >= 0.f) & int(aTimeRgh <= theHit.Time);
+      aTimeMax = max (aNodeMin2, aNodeMax2);
+      aTimeMin = min (aNodeMin2, aNodeMax2);
 
-      if (bool(aHitLft & aHitRgh))
+      aTimeLeave = min (aTimeMax.x, min (aTimeMax.y, aTimeMax.z));
+      aTimeEnter = max (aTimeMin.x, max (aTimeMin.y, aTimeMin.z));
+
+      aHitTimes.z = mix (MAXFLOAT, aTimeEnter,
+        aTimeEnter <= aTimeLeave && aTimeEnter <= theHit.Time && aTimeLeave >= 0.f && aData.z > 1);
+
+      aTimeMax = max (aNodeMin3, aNodeMax3);
+      aTimeMin = min (aNodeMin3, aNodeMax3);
+
+      aTimeLeave = min (aTimeMax.x, min (aTimeMax.y, aTimeMax.z));
+      aTimeEnter = max (aTimeMin.x, max (aTimeMin.y, aTimeMin.z));
+
+      aHitTimes.w = mix (MAXFLOAT, aTimeEnter,
+        aTimeEnter <= aTimeLeave && aTimeEnter <= theHit.Time && aTimeLeave >= 0.f && aData.z > 2);
+
+      ivec4 aChildren = ivec4 (0, 1, 2, 3);
+
+      aChildren.xy = aHitTimes.y < aHitTimes.x ? aChildren.yx : aChildren.xy;
+      aHitTimes.xy = aHitTimes.y < aHitTimes.x ? aHitTimes.yx : aHitTimes.xy;
+      aChildren.zw = aHitTimes.w < aHitTimes.z ? aChildren.wz : aChildren.zw;
+      aHitTimes.zw = aHitTimes.w < aHitTimes.z ? aHitTimes.wz : aHitTimes.zw;
+      aChildren.xz = aHitTimes.z < aHitTimes.x ? aChildren.zx : aChildren.xz;
+      aHitTimes.xz = aHitTimes.z < aHitTimes.x ? aHitTimes.zx : aHitTimes.xz;
+      aChildren.yw = aHitTimes.w < aHitTimes.y ? aChildren.wy : aChildren.yw;
+      aHitTimes.yw = aHitTimes.w < aHitTimes.y ? aHitTimes.wy : aHitTimes.yw;
+      aChildren.yz = aHitTimes.z < aHitTimes.y ? aChildren.zy : aChildren.yz;
+      aHitTimes.yz = aHitTimes.z < aHitTimes.y ? aHitTimes.zy : aHitTimes.yz;
+
+      if (aHitTimes.x != MAXFLOAT)
       {
-        aNode = (aTimeLft < aTimeRgh) ? aData.y : aData.z;
-        
-        Stack[++aHead] = (aTimeLft < aTimeRgh) ? aData.z : aData.y;
+        int aHitMask = (aHitTimes.w != MAXFLOAT ? aChildren.w : aChildren.z) << 2
+                     | (aHitTimes.z != MAXFLOAT ? aChildren.z : aChildren.y);
+
+        if (aHitTimes.y != MAXFLOAT)
+          Stack[++aHead] = aData.y | (aHitMask << 2 | aChildren.y) << 26;
+
+        aNode = aData.y + aChildren.x;
       }
       else
       {
-        if (bool(aHitLft | aHitRgh))
-        {
-          aNode = bool(aHitLft) ? aData.y : aData.z;
-        }
-        else
+        toContinue = (aHead >= 0);
+
+        if (aHead == aStop) // go to top-level BVH
         {
-          if (aHead == theSentinel)
-            return aTriIndex;
-            
-          aNode = Stack[aHead--];
+          aStop = -1; aSubTree = SSubTree (theRay, theInverse, EMPTY_ROOT);
         }
+
+        if (aHead >= 0)
+          aNode = pop (aHead);
       }
     }
-    else // if leaf node
+    else if (aData.x < 0) // leaf node (contains triangles)
     {
       vec3 aNormal;
-      vec2 aParams;
-            
+      vec3 aTimeUV;
+
       for (int anIdx = aData.y; anIdx <= aData.z; ++anIdx)
       {
-        ivec4 aTriangle = texelFetch (uGeometryTriangTexture, anIdx + theTrgOffset);
-
-        vec3 aPoint0 = texelFetch (uGeometryVertexTexture, aTriangle.x + theVrtOffset).xyz;
-        vec3 aPoint1 = texelFetch (uGeometryVertexTexture, aTriangle.y + theVrtOffset).xyz;
-        vec3 aPoint2 = texelFetch (uGeometryVertexTexture, aTriangle.z + theVrtOffset).xyz;
-
-        float aTime = IntersectTriangle (theRay,
-                                         aPoint0,
-                                         aPoint1,
-                                         aPoint2,
-                                         aParams,
-                                         aNormal);
-                                         
-        if (aTime < theHit.Time)
+        ivec4 aTriangle = texelFetch (uGeometryTriangTexture, anIdx + TRG_OFFSET (aSubTree));
+
+        vec3 aPoint0 = texelFetch (uGeometryVertexTexture, aTriangle.x += VRT_OFFSET (aSubTree)).xyz;
+        vec3 aPoint1 = texelFetch (uGeometryVertexTexture, aTriangle.y += VRT_OFFSET (aSubTree)).xyz;
+        vec3 aPoint2 = texelFetch (uGeometryVertexTexture, aTriangle.z += VRT_OFFSET (aSubTree)).xyz;
+
+        IntersectTriangle (aSubTree.TrsfRay, aPoint0, aPoint1, aPoint2, aTimeUV, aNormal);
+
+        if (aTimeUV.x < theHit.Time)
         {
           aTriIndex = aTriangle;
-          
-          theHit = SIntersect (aTime, aParams, aNormal);
+
+          theTrsfId = TRS_OFFSET (aSubTree);
+
+          theHit = SIntersect (aTimeUV.x, aTimeUV.yz, aNormal);
         }
       }
-      
-      if (aHead == theSentinel)
-        return aTriIndex;
 
-      aNode = Stack[aHead--];
+      toContinue = (aHead >= 0);
+
+      if (aHead == aStop) // go to top-level BVH
+      {
+        aStop = -1; aSubTree = SSubTree (theRay, theInverse, EMPTY_ROOT);
+      }
+
+      if (aHead >= 0)
+        aNode = pop (aHead);
+    }
+    else if (aData.x > 0) // switch node
+    {
+      aSubTree.SubData = ivec4 (4 * aData.x - 4, aData.yzw); // store BVH sub-root
+
+      vec4 aInvTransf0 = texelFetch (uSceneTransformTexture, TRS_OFFSET (aSubTree) + 0);
+      vec4 aInvTransf1 = texelFetch (uSceneTransformTexture, TRS_OFFSET (aSubTree) + 1);
+      vec4 aInvTransf2 = texelFetch (uSceneTransformTexture, TRS_OFFSET (aSubTree) + 2);
+      vec4 aInvTransf3 = texelFetch (uSceneTransformTexture, TRS_OFFSET (aSubTree) + 3);
+
+      aSubTree.TrsfRay.Direct = MatrixColMultiplyDir (theRay.Direct,
+                                                      aInvTransf0,
+                                                      aInvTransf1,
+                                                      aInvTransf2);
+
+      aSubTree.Inverse = mix (-UNIT, UNIT, step (ZERO, aSubTree.TrsfRay.Direct)) /
+        max (abs (aSubTree.TrsfRay.Direct), SMALL);
+
+      aSubTree.TrsfRay.Origin = MatrixColMultiplyPnt (theRay.Origin,
+                                                      aInvTransf0,
+                                                      aInvTransf1,
+                                                      aInvTransf2,
+                                                      aInvTransf3);
+
+      aNode = BVH_OFFSET (aSubTree); // go to sub-root node
+
+      aStop = aHead; // store current stack pointer
     }
   }
 
@@ -286,290 +578,182 @@ ivec4 ObjectNearestHit (in int theBVHOffset, in int theVrtOffset, in int theTrgO
 }
 
 // =======================================================================
-// function : ObjectAnyHit
-// purpose  : Finds intersection with any object triangle
+// function : SceneAnyHit
+// purpose  : Finds intersection with any scene triangle
 // =======================================================================
-float ObjectAnyHit (in int theBVHOffset, in int theVrtOffset, in int theTrgOffset,
-  in SRay theRay, in vec3 theInverse, in float theDistance, in int theSentinel)
+float SceneAnyHit (in SRay theRay, in vec3 theInverse, in float theDistance)
 {
-  int aHead = theSentinel; // stack pointer
-  int aNode = 0;           // node to visit
+  float aFactor = 1.f;
+
+  int aNode =  0; // node to traverse
+  int aHead = -1; // pointer of stack
+  int aStop = -1; // BVH level switch
 
-  float aTimeOut;
-  float aTimeLft;
-  float aTimeRgh;
+  SSubTree aSubTree = SSubTree (theRay, theInverse, EMPTY_ROOT);
 
-  while (true)
+  for (bool toContinue = true; toContinue; /* none */)
   {
-    ivec4 aData = texelFetch (uObjectNodeInfoTexture, aNode + theBVHOffset);
+    ivec4 aData = texelFetch (uSceneNodeInfoTexture, aNode);
 
     if (aData.x == 0) // if inner node
     {
-      vec3 aNodeMinLft = texelFetch (uObjectMinPointTexture, aData.y + theBVHOffset).xyz;
-      vec3 aNodeMaxLft = texelFetch (uObjectMaxPointTexture, aData.y + theBVHOffset).xyz;
-      vec3 aNodeMinRgh = texelFetch (uObjectMinPointTexture, aData.z + theBVHOffset).xyz;
-      vec3 aNodeMaxRgh = texelFetch (uObjectMaxPointTexture, aData.z + theBVHOffset).xyz;
+      aData.y += BVH_OFFSET (aSubTree);
 
-      vec3 aTime0 = (aNodeMinLft - theRay.Origin) * theInverse;
-      vec3 aTime1 = (aNodeMaxLft - theRay.Origin) * theInverse;
+      vec4 aHitTimes = vec4 (MAXFLOAT,
+                             MAXFLOAT,
+                             MAXFLOAT,
+                             MAXFLOAT);
 
-      vec3 aTimeMax = max (aTime0, aTime1);
-      vec3 aTimeMin = min (aTime0, aTime1);
+      vec3 aRayOriginInverse = -aSubTree.TrsfRay.Origin * aSubTree.Inverse;
 
-      aTime0 = (aNodeMinRgh - theRay.Origin) * theInverse;
-      aTime1 = (aNodeMaxRgh - theRay.Origin) * theInverse;
-      
-      aTimeOut = min (aTimeMax.x, min (aTimeMax.y, aTimeMax.z));
-      aTimeLft = max (aTimeMin.x, max (aTimeMin.y, aTimeMin.z));
+      vec3 aNodeMin0 = texelFetch (uSceneMinPointTexture, aData.y +                0).xyz * aSubTree.Inverse + aRayOriginInverse;
+      vec3 aNodeMin1 = texelFetch (uSceneMinPointTexture, aData.y +                1).xyz * aSubTree.Inverse + aRayOriginInverse;
+      vec3 aNodeMin2 = texelFetch (uSceneMinPointTexture, aData.y + min (2, aData.z)).xyz * aSubTree.Inverse + aRayOriginInverse;
+      vec3 aNodeMin3 = texelFetch (uSceneMinPointTexture, aData.y + min (3, aData.z)).xyz * aSubTree.Inverse + aRayOriginInverse;
+      vec3 aNodeMax0 = texelFetch (uSceneMaxPointTexture, aData.y +                0).xyz * aSubTree.Inverse + aRayOriginInverse;
+      vec3 aNodeMax1 = texelFetch (uSceneMaxPointTexture, aData.y +                1).xyz * aSubTree.Inverse + aRayOriginInverse;
+      vec3 aNodeMax2 = texelFetch (uSceneMaxPointTexture, aData.y + min (2, aData.z)).xyz * aSubTree.Inverse + aRayOriginInverse;
+      vec3 aNodeMax3 = texelFetch (uSceneMaxPointTexture, aData.y + min (3, aData.z)).xyz * aSubTree.Inverse + aRayOriginInverse;
 
-      int aHitLft = int(aTimeLft <= aTimeOut) & int(aTimeOut >= 0.f) & int(aTimeLft <= theDistance);
+      vec3 aTimeMax = max (aNodeMin0, aNodeMax0);
+      vec3 aTimeMin = min (aNodeMin0, aNodeMax0);
 
-      aTimeMax = max (aTime0, aTime1);
-      aTimeMin = min (aTime0, aTime1);
+      float aTimeLeave = min (aTimeMax.x, min (aTimeMax.y, aTimeMax.z));
+      float aTimeEnter = max (aTimeMin.x, max (aTimeMin.y, aTimeMin.z));
 
-      aTimeOut = min (aTimeMax.x, min (aTimeMax.y, aTimeMax.z));
-      aTimeRgh = max (aTimeMin.x, max (aTimeMin.y, aTimeMin.z));
+      aHitTimes.x = mix (MAXFLOAT, aTimeEnter,
+        aTimeEnter <= aTimeLeave && aTimeEnter <= theDistance && aTimeLeave >= 0.f);
 
-      int aHitRgh = int(aTimeRgh <= aTimeOut) & int(aTimeOut >= 0.f) & int(aTimeRgh <= theDistance);
+      aTimeMax = max (aNodeMin1, aNodeMax1);
+      aTimeMin = min (aNodeMin1, aNodeMax1);
 
-      if (bool(aHitLft & aHitRgh))
-      {
-        aNode = (aTimeLft < aTimeRgh) ? aData.y : aData.z;
+      aTimeLeave = min (aTimeMax.x, min (aTimeMax.y, aTimeMax.z));
+      aTimeEnter = max (aTimeMin.x, max (aTimeMin.y, aTimeMin.z));
 
-        Stack[++aHead] = (aTimeLft < aTimeRgh) ? aData.z : aData.y;
-      }
-      else
-      {
-        if (bool(aHitLft | aHitRgh))
-        {
-          aNode = bool(aHitLft) ? aData.y : aData.z;
-        }
-        else
-        {
-          if (aHead == theSentinel)
-            return 1.f;
+      aHitTimes.y = mix (MAXFLOAT, aTimeEnter,
+        aTimeEnter <= aTimeLeave && aTimeEnter <= theDistance && aTimeLeave >= 0.f);
 
-          aNode = Stack[aHead--];
-        }
-      }
-    }
-    else // if leaf node
-    {
-      vec3 aNormal;
-      vec2 aParams;
-      
-      for (int anIdx = aData.y; anIdx <= aData.z; ++anIdx)
-      {
-        ivec4 aTriangle = texelFetch (uGeometryTriangTexture, anIdx + theTrgOffset);
-
-        vec3 aPoint0 = texelFetch (uGeometryVertexTexture, aTriangle.x + theVrtOffset).xyz;
-        vec3 aPoint1 = texelFetch (uGeometryVertexTexture, aTriangle.y + theVrtOffset).xyz;
-        vec3 aPoint2 = texelFetch (uGeometryVertexTexture, aTriangle.z + theVrtOffset).xyz;
-
-        float aTime = IntersectTriangle (theRay,
-                                         aPoint0,
-                                         aPoint1,
-                                         aPoint2,
-                                         aParams,
-                                         aNormal);
-                                         
-        if (aTime < theDistance)
-          return 0.f;
-      }
-      
-      if (aHead == theSentinel)
-        return 1.f;
+      aTimeMax = max (aNodeMin2, aNodeMax2);
+      aTimeMin = min (aNodeMin2, aNodeMax2);
 
-      aNode = Stack[aHead--];
-    }
-  }
+      aTimeLeave = min (aTimeMax.x, min (aTimeMax.y, aTimeMax.z));
+      aTimeEnter = max (aTimeMin.x, max (aTimeMin.y, aTimeMin.z));
 
-  return 1.f;
-}
+      aHitTimes.z = mix (MAXFLOAT, aTimeEnter,
+        aTimeEnter <= aTimeLeave && aTimeEnter <= theDistance && aTimeLeave >= 0.f && aData.z > 1);
 
-// =======================================================================
-// function : SceneNearestHit
-// purpose  : Finds intersection with nearest scene triangle
-// =======================================================================
-ivec4 SceneNearestHit (in SRay theRay, in vec3 theInverse, inout SIntersect theHit)
-{
-  int aHead = -1; // stack pointer
-  int aNode =  0; // node to visit
+      aTimeMax = max (aNodeMin3, aNodeMax3);
+      aTimeMin = min (aNodeMin3, aNodeMax3);
 
-  ivec4 aHitObject = INALID_HIT;
-  
-  float aTimeOut;
-  float aTimeLft;
-  float aTimeRgh;
+      aTimeLeave = min (aTimeMax.x, min (aTimeMax.y, aTimeMax.z));
+      aTimeEnter = max (aTimeMin.x, max (aTimeMin.y, aTimeMin.z));
 
-  while (true)
-  {
-    ivec4 aData = texelFetch (uSceneNodeInfoTexture, aNode);
+      aHitTimes.w = mix (MAXFLOAT, aTimeEnter,
+        aTimeEnter <= aTimeLeave && aTimeEnter <= theDistance && aTimeLeave >= 0.f && aData.z > 2);
 
-    if (aData.x != 0) // if leaf node
-    {
-      vec3 aNodeMin = texelFetch (uSceneMinPointTexture, aNode).xyz;
-      vec3 aNodeMax = texelFetch (uSceneMaxPointTexture, aNode).xyz;
-      
-      vec3 aTime0 = (aNodeMin - theRay.Origin) * theInverse;
-      vec3 aTime1 = (aNodeMax - theRay.Origin) * theInverse;
-      
-      vec3 aTimes = min (aTime0, aTime1);
-      
-      if (max (aTimes.x, max (aTimes.y, aTimes.z)) < theHit.Time)
+      ivec4 aChildren = ivec4 (0, 1, 2, 3);
+
+      aChildren.xy = aHitTimes.y < aHitTimes.x ? aChildren.yx : aChildren.xy;
+      aHitTimes.xy = aHitTimes.y < aHitTimes.x ? aHitTimes.yx : aHitTimes.xy;
+      aChildren.zw = aHitTimes.w < aHitTimes.z ? aChildren.wz : aChildren.zw;
+      aHitTimes.zw = aHitTimes.w < aHitTimes.z ? aHitTimes.wz : aHitTimes.zw;
+      aChildren.xz = aHitTimes.z < aHitTimes.x ? aChildren.zx : aChildren.xz;
+      aHitTimes.xz = aHitTimes.z < aHitTimes.x ? aHitTimes.zx : aHitTimes.xz;
+      aChildren.yw = aHitTimes.w < aHitTimes.y ? aChildren.wy : aChildren.yw;
+      aHitTimes.yw = aHitTimes.w < aHitTimes.y ? aHitTimes.wy : aHitTimes.yw;
+      aChildren.yz = aHitTimes.z < aHitTimes.y ? aChildren.zy : aChildren.yz;
+      aHitTimes.yz = aHitTimes.z < aHitTimes.y ? aHitTimes.zy : aHitTimes.yz;
+
+      if (aHitTimes.x != MAXFLOAT)
+      {
+        int aHitMask = (aHitTimes.w != MAXFLOAT ? aChildren.w : aChildren.z) << 2
+                     | (aHitTimes.z != MAXFLOAT ? aChildren.z : aChildren.y);
+
+        if (aHitTimes.y != MAXFLOAT)
+          Stack[++aHead] = aData.y | (aHitMask << 2 | aChildren.y) << 26;
+
+        aNode = aData.y + aChildren.x;
+      }
+      else
       {
-        ivec4 aTriIndex = ObjectNearestHit (
-          aData.y, aData.z, aData.w, theRay, theInverse, theHit, aHead);
+        toContinue = (aHead >= 0);
 
-        if (aTriIndex.x != -1)
+        if (aHead == aStop) // go to top-level BVH
         {
-          aHitObject = ivec4 (aTriIndex.x + aData.z,  // vertex 0
-                              aTriIndex.y + aData.z,  // vertex 1
-                              aTriIndex.z + aData.z,  // vertex 2
-                              aTriIndex.w);           // material
+          aStop = -1; aSubTree = SSubTree (theRay, theInverse, EMPTY_ROOT);
         }
+
+        if (aHead >= 0)
+          aNode = pop (aHead);
       }
-      
-      if (aHead < 0)
-        return aHitObject;
-            
-      aNode = Stack[aHead--];
     }
-    else // if inner node
+    else if (aData.x < 0) // leaf node
     {
-      vec3 aNodeMinLft = texelFetch (uSceneMinPointTexture, aData.y).xyz;
-      vec3 aNodeMaxLft = texelFetch (uSceneMaxPointTexture, aData.y).xyz;
-      vec3 aNodeMinRgh = texelFetch (uSceneMinPointTexture, aData.z).xyz;
-      vec3 aNodeMaxRgh = texelFetch (uSceneMaxPointTexture, aData.z).xyz;
-
-      vec3 aTime0 = (aNodeMinLft - theRay.Origin) * theInverse;
-      vec3 aTime1 = (aNodeMaxLft - theRay.Origin) * theInverse;
-
-      vec3 aTimeMax = max (aTime0, aTime1);
-      vec3 aTimeMin = min (aTime0, aTime1);
-
-      aTimeOut = min (aTimeMax.x, min (aTimeMax.y, aTimeMax.z));
-      aTimeLft = max (aTimeMin.x, max (aTimeMin.y, aTimeMin.z));
-
-      int aHitLft = int(aTimeLft <= aTimeOut) & int(aTimeOut >= 0.f) & int(aTimeLft <= theHit.Time);
-      
-      aTime0 = (aNodeMinRgh - theRay.Origin) * theInverse;
-      aTime1 = (aNodeMaxRgh - theRay.Origin) * theInverse;
+      vec3 aNormal;
+      vec3 aTimeUV;
 
-      aTimeMax = max (aTime0, aTime1);
-      aTimeMin = min (aTime0, aTime1);
+      for (int anIdx = aData.y; anIdx <= aData.z; ++anIdx)
+      {
+        ivec4 aTriangle = texelFetch (uGeometryTriangTexture, anIdx + TRG_OFFSET (aSubTree));
 
-      aTimeOut = min (aTimeMax.x, min (aTimeMax.y, aTimeMax.z));
-      aTimeRgh = max (aTimeMin.x, max (aTimeMin.y, aTimeMin.z));
-      
-      int aHitRgh = int(aTimeRgh <= aTimeOut) & int(aTimeOut >= 0.f) & int(aTimeRgh <= theHit.Time);
+        vec3 aPoint0 = texelFetch (uGeometryVertexTexture, aTriangle.x += VRT_OFFSET (aSubTree)).xyz;
+        vec3 aPoint1 = texelFetch (uGeometryVertexTexture, aTriangle.y += VRT_OFFSET (aSubTree)).xyz;
+        vec3 aPoint2 = texelFetch (uGeometryVertexTexture, aTriangle.z += VRT_OFFSET (aSubTree)).xyz;
 
-      if (bool(aHitLft & aHitRgh))
-      {
-        aNode = (aTimeLft < aTimeRgh) ? aData.y : aData.z;
+        IntersectTriangle (aSubTree.TrsfRay, aPoint0, aPoint1, aPoint2, aTimeUV, aNormal);
 
-        Stack[++aHead] = (aTimeLft < aTimeRgh) ? aData.z : aData.y;
-      }
-      else
-      {
-        if (bool(aHitLft | aHitRgh))
+#ifdef TRANSPARENT_SHADOWS
+        if (aTimeUV.x < theDistance)
         {
-          aNode = bool(aHitLft) ? aData.y : aData.z;
+          aFactor *= 1.f - texelFetch (uRaytraceMaterialTexture, MATERIAL_TRAN (aTriangle.w)).x;
         }
-        else
+#else
+        if (aTimeUV.x < theDistance)
         {
-          if (aHead < 0)
-            return aHitObject;
-
-          aNode = Stack[aHead--];
+          aFactor = 0.f;
         }
+#endif
       }
-    }
-  }
-  
-  return aHitObject;
-}
 
-// =======================================================================
-// function : SceneAnyHit
-// purpose  : Finds intersection with any scene triangle
-// =======================================================================
-float SceneAnyHit (in SRay theRay, in vec3 theInverse, in float theDistance)
-{
-  int aHead = -1; // stack pointer
-  int aNode =  0; // node to visit
-  
-  float aTimeOut;
-  float aTimeLft;
-  float aTimeRgh;
-
-  while (true)
-  {
-    ivec4 aData = texelFetch (uSceneNodeInfoTexture, aNode);
+      toContinue = (aHead >= 0) && (aFactor > 0.1f);
 
-    if (aData.x != 0) // if leaf node
-    {
-      bool isShadow = 0.f == ObjectAnyHit (
-        aData.y, aData.z, aData.w, theRay, theInverse, theDistance, aHead);
-        
-      if (aHead < 0 || isShadow)
-        return isShadow ? 0.f : 1.f;
-            
-      aNode = Stack[aHead--];
+      if (aHead == aStop) // go to top-level BVH
+      {
+        aStop = -1; aSubTree = SSubTree (theRay, theInverse, EMPTY_ROOT);
+      }
+
+      if (aHead >= 0)
+        aNode = pop (aHead);
     }
-    else // if inner node
+    else if (aData.x > 0) // switch node
     {
-      vec3 aNodeMinLft = texelFetch (uSceneMinPointTexture, aData.y).xyz;
-      vec3 aNodeMaxLft = texelFetch (uSceneMaxPointTexture, aData.y).xyz;
-      vec3 aNodeMinRgh = texelFetch (uSceneMinPointTexture, aData.z).xyz;
-      vec3 aNodeMaxRgh = texelFetch (uSceneMaxPointTexture, aData.z).xyz;
-      
-      vec3 aTime0 = (aNodeMinLft - theRay.Origin) * theInverse;
-      vec3 aTime1 = (aNodeMaxLft - theRay.Origin) * theInverse;
-
-      vec3 aTimeMax = max (aTime0, aTime1);
-      vec3 aTimeMin = min (aTime0, aTime1);
-
-      aTimeOut = min (aTimeMax.x, min (aTimeMax.y, aTimeMax.z));
-      aTimeLft = max (aTimeMin.x, max (aTimeMin.y, aTimeMin.z));
-
-      int aHitLft = int(aTimeLft <= aTimeOut) & int(aTimeOut >= 0.f) & int(aTimeLft <= theDistance);
-      
-      aTime0 = (aNodeMinRgh - theRay.Origin) * theInverse;
-      aTime1 = (aNodeMaxRgh - theRay.Origin) * theInverse;
-
-      aTimeMax = max (aTime0, aTime1);
-      aTimeMin = min (aTime0, aTime1);
-
-      aTimeOut = min (aTimeMax.x, min (aTimeMax.y, aTimeMax.z));
-      aTimeRgh = max (aTimeMin.x, max (aTimeMin.y, aTimeMin.z));
-      
-      int aHitRgh = int(aTimeRgh <= aTimeOut) & int(aTimeOut >= 0.f) & int(aTimeRgh <= theDistance);
-
-      if (bool(aHitLft & aHitRgh))
-      {
-        aNode = (aTimeLft < aTimeRgh) ? aData.y : aData.z;
+      aSubTree.SubData = ivec4 (4 * aData.x - 4, aData.yzw); // store BVH sub-root
 
-        Stack[++aHead] = (aTimeLft < aTimeRgh) ? aData.z : aData.y;
-      }
-      else
-      {
-        if (bool(aHitLft | aHitRgh))
-        {
-          aNode = bool(aHitLft) ? aData.y : aData.z;
-        }
-        else
-        {
-          if (aHead < 0)
-            return 1.f;
+      vec4 aInvTransf0 = texelFetch (uSceneTransformTexture, TRS_OFFSET (aSubTree) + 0);
+      vec4 aInvTransf1 = texelFetch (uSceneTransformTexture, TRS_OFFSET (aSubTree) + 1);
+      vec4 aInvTransf2 = texelFetch (uSceneTransformTexture, TRS_OFFSET (aSubTree) + 2);
+      vec4 aInvTransf3 = texelFetch (uSceneTransformTexture, TRS_OFFSET (aSubTree) + 3);
 
-          aNode = Stack[aHead--];
-        }
-      }
+      aSubTree.TrsfRay.Direct = MatrixColMultiplyDir (theRay.Direct,
+                                                      aInvTransf0,
+                                                      aInvTransf1,
+                                                      aInvTransf2);
+
+      aSubTree.TrsfRay.Origin = MatrixColMultiplyPnt (theRay.Origin,
+                                                      aInvTransf0,
+                                                      aInvTransf1,
+                                                      aInvTransf2,
+                                                      aInvTransf3);
+
+      aSubTree.Inverse = mix (-UNIT, UNIT, step (ZERO, aSubTree.TrsfRay.Direct)) / max (abs (aSubTree.TrsfRay.Direct), SMALL);
+
+      aNode = BVH_OFFSET (aSubTree); // go to sub-root node
+
+      aStop = aHead; // store current stack pointer
     }
   }
-  
-  return 1.f;
+
+  return aFactor;
 }
 
 #define PI 3.1415926f
@@ -581,9 +765,9 @@ float SceneAnyHit (in SRay theRay, in vec3 theInverse, in float theDistance)
 vec2 Latlong (in vec3 thePoint, in float theRadius)
 {
   float aPsi = acos (-thePoint.z / theRadius);
-  
+
   float aPhi = atan (thePoint.y, thePoint.x) + PI;
-  
+
   return vec2 (aPhi * 0.1591549f,
                aPsi * 0.3183098f);
 }
@@ -597,172 +781,284 @@ vec3 SmoothNormal (in vec2 theUV, in ivec4 theTriangle)
   vec3 aNormal0 = texelFetch (uGeometryNormalTexture, theTriangle.x).xyz;
   vec3 aNormal1 = texelFetch (uGeometryNormalTexture, theTriangle.y).xyz;
   vec3 aNormal2 = texelFetch (uGeometryNormalTexture, theTriangle.z).xyz;
-  
+
   return normalize (aNormal1 * theUV.x +
                     aNormal2 * theUV.y +
-                    aNormal0 * (1.f - theUV.x - theUV.y));
+                    aNormal0 * (1.0f - theUV.x - theUV.y));
+}
+
+// =======================================================================
+// function : SmoothUV
+// purpose  : Interpolates UV coordinates across the triangle
+// =======================================================================
+#ifdef USE_TEXTURES
+vec2 SmoothUV (in vec2 theUV, in ivec4 theTriangle)
+{
+  vec2 aTexCrd0 = texelFetch (uGeometryTexCrdTexture, theTriangle.x).st;
+  vec2 aTexCrd1 = texelFetch (uGeometryTexCrdTexture, theTriangle.y).st;
+  vec2 aTexCrd2 = texelFetch (uGeometryTexCrdTexture, theTriangle.z).st;
+
+  return aTexCrd1 * theUV.x +
+         aTexCrd2 * theUV.y +
+         aTexCrd0 * (1.0f - theUV.x - theUV.y);
 }
+#endif
+
+// =======================================================================
+// function : FetchEnvironment
+// purpose  :
+// =======================================================================
+vec4 FetchEnvironment (in vec2 theTexCoord)
+{
+  return mix (vec4 (0.0f, 0.0f, 0.0f, 1.0f),
+    textureLod (uEnvironmentMapTexture, theTexCoord, 0.0f), float (uSphereMapEnabled));
+}
+
+// =======================================================================
+// function : Refract
+// purpose  : Computes refraction ray (also handles TIR)
+// =======================================================================
+#ifndef PATH_TRACING
+vec3 Refract (in vec3 theInput,
+              in vec3 theNormal,
+              in float theRefractIndex,
+              in float theInvRefractIndex)
+{
+  float aNdotI = dot (theInput, theNormal);
 
-#define THRESHOLD vec3 (0.1f, 0.1f, 0.1f)
+  float anIndex = aNdotI < 0.0f
+                ? theInvRefractIndex
+                : theRefractIndex;
 
-#define MATERIAL_AMBN(index) (7 * index + 0)
-#define MATERIAL_DIFF(index) (7 * index + 1)
-#define MATERIAL_SPEC(index) (7 * index + 2)
-#define MATERIAL_EMIS(index) (7 * index + 3)
-#define MATERIAL_REFL(index) (7 * index + 4)
-#define MATERIAL_REFR(index) (7 * index + 5)
-#define MATERIAL_TRAN(index) (7 * index + 6)
+  float aSquare = anIndex * anIndex * (1.0f - aNdotI * aNdotI);
+
+  if (aSquare > 1.0f)
+  {
+    return reflect (theInput, theNormal);
+  }
+
+  float aNdotT = sqrt (1.0f - aSquare);
+
+  return normalize (anIndex * theInput -
+    (anIndex * aNdotI + (aNdotI < 0.0f ? aNdotT : -aNdotT)) * theNormal);
+}
+#endif
+
+#define MIN_SLOPE 0.0001f
+#define EPS_SCALE 8.0000f
+
+#define THRESHOLD vec3 (0.1f)
+
+#define INVALID_BOUNCES 1000
 
 #define LIGHT_POS(index) (2 * index + 1)
 #define LIGHT_PWR(index) (2 * index + 0)
 
 // =======================================================================
 // function : Radiance
-// purpose  : Computes color of specified ray
+// purpose  : Computes color along the given ray
 // =======================================================================
+#ifndef PATH_TRACING
 vec4 Radiance (in SRay theRay, in vec3 theInverse)
 {
-  vec3 aResult = vec3 (0.f);
-  vec4 aWeight = vec4 (1.f);
-  
-  for (int aDepth = 0; aDepth < 5; ++aDepth)
+  vec3 aResult = vec3 (0.0f);
+  vec4 aWeight = vec4 (1.0f);
+
+  int aTrsfId;
+
+  float aRaytraceDepth = MAXFLOAT;
+
+  for (int aDepth = 0; aDepth < NB_BOUNCES; ++aDepth)
   {
     SIntersect aHit = SIntersect (MAXFLOAT, vec2 (ZERO), ZERO);
-    
-    ivec4 aTriIndex = SceneNearestHit (theRay, theInverse, aHit);
+
+    ivec4 aTriIndex = SceneNearestHit (theRay, theInverse, aHit, aTrsfId);
 
     if (aTriIndex.x == -1)
     {
-      if (aWeight.w != 0.f)
-      {
-        return vec4 (aResult.x,
-                     aResult.y,
-                     aResult.z,
-                     aWeight.w);
-      }
+      vec4 aColor = vec4 (0.0);
 
-      if (bool(uEnvironmentEnable))
+      if (bool(uSphereMapForBack) || aWeight.w == 0.0f /* reflection */)
       {
         float aTime = IntersectSphere (theRay, uSceneRadius);
-        
-        aResult.xyz += aWeight.xyz * textureLod (uEnvironmentMapTexture,
-          Latlong (theRay.Direct * aTime + theRay.Origin, uSceneRadius), 0.f).xyz;
+
+        aColor = FetchEnvironment (Latlong (
+          theRay.Direct * aTime + theRay.Origin, uSceneRadius));
+      }
+      else
+      {
+        aColor = BackgroundColor();
       }
-      
-      return vec4 (aResult.x,
-                   aResult.y,
-                   aResult.z,
-                   aWeight.w);
+
+      aResult += aWeight.xyz * aColor.xyz; aWeight.w *= aColor.w;
+
+      break; // terminate path
     }
-    
-    vec3 aPoint = theRay.Direct * aHit.Time + theRay.Origin;
-    
-    vec3 aAmbient = vec3 (texelFetch (
-      uRaytraceMaterialTexture, MATERIAL_AMBN (aTriIndex.w)));
-    vec3 aDiffuse = vec3 (texelFetch (
-      uRaytraceMaterialTexture, MATERIAL_DIFF (aTriIndex.w)));
-    vec4 aSpecular = vec4 (texelFetch (
-      uRaytraceMaterialTexture, MATERIAL_SPEC (aTriIndex.w)));
-    vec2 aOpacity = vec2 (texelFetch (
-      uRaytraceMaterialTexture, MATERIAL_TRAN (aTriIndex.w)));
-      
+
+    vec3 aInvTransf0 = texelFetch (uSceneTransformTexture, aTrsfId + 0).xyz;
+    vec3 aInvTransf1 = texelFetch (uSceneTransformTexture, aTrsfId + 1).xyz;
+    vec3 aInvTransf2 = texelFetch (uSceneTransformTexture, aTrsfId + 2).xyz;
+
+    aHit.Normal = normalize (vec3 (dot (aInvTransf0, aHit.Normal),
+                                   dot (aInvTransf1, aHit.Normal),
+                                   dot (aInvTransf2, aHit.Normal)));
+
+    theRay.Origin += theRay.Direct * aHit.Time; // intersection point
+
+    // Evaluate depth on first hit
+    if (aDepth == 0)
+    {
+      // For polygons that are parallel to the screen plane, the depth slope
+      // is equal to 1, resulting in small polygon offset. For polygons that
+      // that are at a large angle to the screen, the depth slope tends to 1,
+      // resulting in a larger polygon offset
+      float aPolygonOffset = uSceneEpsilon * EPS_SCALE /
+        max (abs (dot (theRay.Direct, aHit.Normal)), MIN_SLOPE);
+
+      // Hit point in NDC-space [-1,1] (the polygon offset is applied in the world space)
+      vec4 aNDCPoint = uViewMat * vec4 (theRay.Origin + theRay.Direct * aPolygonOffset, 1.f);
+
+      aRaytraceDepth = (aNDCPoint.z / aNDCPoint.w) * 0.5f + 0.5f;
+    }
+
     vec3 aNormal = SmoothNormal (aHit.UV, aTriIndex);
-    
-    aHit.Normal = normalize (aHit.Normal);
-    
+
+    aNormal = normalize (vec3 (dot (aInvTransf0, aNormal),
+                               dot (aInvTransf1, aNormal),
+                               dot (aInvTransf2, aNormal)));
+
+    vec3 aAmbient  = texelFetch (
+      uRaytraceMaterialTexture, MATERIAL_AMBN (aTriIndex.w)).rgb;
+    vec4 aDiffuse  = texelFetch (
+      uRaytraceMaterialTexture, MATERIAL_DIFF (aTriIndex.w));
+    vec4 aSpecular = texelFetch (
+      uRaytraceMaterialTexture, MATERIAL_SPEC (aTriIndex.w));
+    vec4 aOpacity  = texelFetch (
+      uRaytraceMaterialTexture, MATERIAL_TRAN (aTriIndex.w));
+
+#ifdef USE_TEXTURES
+    if (aDiffuse.w >= 0.f)
+    {
+      vec4 aTexCoord = vec4 (SmoothUV (aHit.UV, aTriIndex), 0.f, 1.f);
+
+      vec4 aTrsfRow1 = texelFetch (
+        uRaytraceMaterialTexture, MATERIAL_TRS1 (aTriIndex.w));
+      vec4 aTrsfRow2 = texelFetch (
+        uRaytraceMaterialTexture, MATERIAL_TRS2 (aTriIndex.w));
+
+      aTexCoord.st = vec2 (dot (aTrsfRow1, aTexCoord),
+                           dot (aTrsfRow2, aTexCoord));
+
+      vec3 aTexColor = textureLod (
+        sampler2D (uTextureSamplers[int(aDiffuse.w)]), aTexCoord.st, 0.f).rgb;
+
+      aDiffuse.rgb *= aTexColor;
+      aAmbient.rgb *= aTexColor;
+    }
+#endif
+
+    vec3 aEmission = texelFetch (
+      uRaytraceMaterialTexture, MATERIAL_EMIS (aTriIndex.w)).rgb;
+
+    float aGeomFactor = dot (aNormal, theRay.Direct);
+
+    aResult.xyz += aWeight.xyz * aOpacity.x * (
+      uGlobalAmbient.xyz * aAmbient * max (abs (aGeomFactor), 0.5f) + aEmission);
+
+    vec3 aSidedNormal = mix (aNormal, -aNormal, step (0.0f, aGeomFactor));
+
     for (int aLightIdx = 0; aLightIdx < uLightCount; ++aLightIdx)
     {
       vec4 aLight = texelFetch (
         uRaytraceLightSrcTexture, LIGHT_POS (aLightIdx));
-      
+
       float aDistance = MAXFLOAT;
-      
-      if (aLight.w != 0.f) // point light source
-      {
-        aDistance = length (aLight.xyz -= aPoint);
-        
-        aLight.xyz *= 1.f / aDistance;
-      }
 
-      SRay aShadow = SRay (aPoint + aLight.xyz * uSceneEpsilon, aLight.xyz);
-      
-      aShadow.Origin += aHit.Normal * uSceneEpsilon *
-        (dot (aHit.Normal, aLight.xyz) >= 0.f ? 1.f : -1.f);
-      
-      float aVisibility = 1.f;
-     
-      if (bool(uShadowsEnable))
+      if (aLight.w != 0.0f) // point light source
       {
-        vec3 aInverse = 1.f / max (abs (aLight.xyz), SMALL);
-        
-        aInverse.x = aLight.x < 0.f ? -aInverse.x : aInverse.x;
-        aInverse.y = aLight.y < 0.f ? -aInverse.y : aInverse.y;
-        aInverse.z = aLight.z < 0.f ? -aInverse.z : aInverse.z;
-        
-        aVisibility = SceneAnyHit (aShadow, aInverse, aDistance);
+        aDistance = length (aLight.xyz -= theRay.Origin);
+
+        aLight.xyz *= 1.0f / aDistance;
       }
-      
-      if (aVisibility > 0.f)
+
+      float aLdotN = dot (aLight.xyz, aSidedNormal);
+
+      if (aLdotN > 0.0f) // first check if light source is important
       {
-        vec3 aIntensity = vec3 (texelFetch (
-          uRaytraceLightSrcTexture, LIGHT_PWR (aLightIdx)));
-        float aLdotN = dot (aShadow.Direct, aNormal);
-        
-        if (aOpacity.y > 0.f)    // force two-sided lighting
-          aLdotN = abs (aLdotN); // for transparent surfaces
-          
-        if (aLdotN > 0.f)
+        float aVisibility = 1.0f;
+
+        if (bool(uShadowsEnabled))
         {
-          float aRdotV = dot (reflect (aShadow.Direct, aNormal), theRay.Direct);
-          
-          aResult.xyz += aWeight.xyz * aOpacity.x * aIntensity *
-            (aDiffuse * aLdotN + aSpecular.xyz * pow (max (0.f, aRdotV), aSpecular.w));
+          SRay aShadow = SRay (theRay.Origin, aLight.xyz);
+
+          aShadow.Origin += uSceneEpsilon * (aLight.xyz +
+            mix (-aHit.Normal, aHit.Normal, step (0.0f, dot (aHit.Normal, aLight.xyz))));
+
+          vec3 aInverse = 1.0f / max (abs (aLight.xyz), SMALL);
+
+          aVisibility = SceneAnyHit (
+            aShadow, mix (-aInverse, aInverse, step (ZERO, aLight.xyz)), aDistance);
+        }
+
+        if (aVisibility > 0.0f)
+        {
+          vec3 aIntensity = vec3 (texelFetch (
+            uRaytraceLightSrcTexture, LIGHT_PWR (aLightIdx)));
+
+          float aRdotV = dot (reflect (aLight.xyz, aSidedNormal), theRay.Direct);
+
+          aResult.xyz += aWeight.xyz * (aOpacity.x * aVisibility) * aIntensity *
+            (aDiffuse.xyz * aLdotN + aSpecular.xyz * pow (max (0.f, aRdotV), aSpecular.w));
         }
       }
     }
-    
-    aResult.xyz += aWeight.xyz * uGlobalAmbient.xyz *
-      aAmbient * aOpacity.x * max (abs (dot (aNormal, theRay.Direct)), 0.5f);
-    
-    if (aOpacity.x != 1.f)
+
+    if (aOpacity.x != 1.0f)
     {
       aWeight *= aOpacity.y;
+
+      if (aOpacity.z != 1.0f)
+      {
+        theRay.Direct = Refract (theRay.Direct, aNormal, aOpacity.z, aOpacity.w);
+      }
     }
     else
     {
-      aWeight *= bool(uReflectionsEnable) ?
-        texelFetch (uRaytraceMaterialTexture, MATERIAL_REFL (aTriIndex.w)) : vec4 (0.f);
-      
-      theRay.Direct = reflect (theRay.Direct, aNormal);
-      
-      if (dot (theRay.Direct, aHit.Normal) < 0.f)
+      aWeight *= bool(uReflectEnabled) ?
+        texelFetch (uRaytraceMaterialTexture, MATERIAL_REFL (aTriIndex.w)) : vec4 (0.0f);
+
+      vec3 aReflect = reflect (theRay.Direct, aNormal);
+
+      if (dot (aReflect, aHit.Normal) * dot (theRay.Direct, aHit.Normal) > 0.0f)
       {
-        theRay.Direct = reflect (theRay.Direct, aHit.Normal);      
+        aReflect = reflect (theRay.Direct, aHit.Normal);
       }
 
-      theInverse = 1.0 / max (abs (theRay.Direct), SMALL);
-      
-      theInverse.x = theRay.Direct.x < 0.0 ? -theInverse.x : theInverse.x;
-      theInverse.y = theRay.Direct.y < 0.0 ? -theInverse.y : theInverse.y;
-      theInverse.z = theRay.Direct.z < 0.0 ? -theInverse.z : theInverse.z;
-      
-      aPoint += aHit.Normal * (dot (aHit.Normal, theRay.Direct) >= 0.f ? uSceneEpsilon : -uSceneEpsilon);
+      theRay.Direct = aReflect;
     }
-    
+
     if (all (lessThanEqual (aWeight.xyz, THRESHOLD)))
     {
-      return vec4 (aResult.x,
-                   aResult.y,
-                   aResult.z,
-                   aWeight.w);
+      aDepth = INVALID_BOUNCES;
     }
-    
-    theRay.Origin = theRay.Direct * uSceneEpsilon + aPoint;
+    else if (aOpacity.x == 1.0f || aOpacity.z != 1.0f) // if no simple transparency
+    {
+      theRay.Origin += aHit.Normal * mix (
+        -uSceneEpsilon, uSceneEpsilon, step (0.0f, dot (aHit.Normal, theRay.Direct)));
+
+      theInverse = 1.0f / max (abs (theRay.Direct), SMALL);
+
+      theInverse = mix (-theInverse, theInverse, step (ZERO, theRay.Direct));
+    }
+
+    theRay.Origin += theRay.Direct * uSceneEpsilon;
   }
 
+  gl_FragDepth = aRaytraceDepth;
+
   return vec4 (aResult.x,
                aResult.y,
                aResult.z,
                aWeight.w);
 }
+#endif