0031099: Visualization, TKOpenGl - support Point light source with artistic full...
authoriko <iko@opencascade.com>
Tue, 24 Dec 2019 13:05:00 +0000 (16:05 +0300)
committerbugmaster <bugmaster@opencascade.com>
Mon, 13 Jan 2020 13:07:19 +0000 (16:07 +0300)
"Range" parameter of point light sources (positional and spot) concidering in PBR has been added.
Angular attenuation parameter of spot light in PBR has been reimplemented based on existing "concentration" parameter.

12 files changed:
src/Graphic3d/Graphic3d_CLight.cxx
src/Graphic3d/Graphic3d_CLight.hxx
src/OpenGl/OpenGl_ShaderManager.cxx
src/OpenGl/OpenGl_ShaderManager.hxx
src/OpenGl/OpenGl_View_Raytrace.cxx
src/Shaders/Declarations.glsl
src/Shaders/FILES
src/Shaders/PointLightAttenuation.glsl [new file with mode: 0644]
src/Shaders/Shaders_Declarations_glsl.pxx
src/Shaders/Shaders_PointLightAttenuation_glsl.pxx [new file with mode: 0644]
src/ViewerTest/ViewerTest_ViewerCommands.cxx
tests/v3d/glsl/pbr_attenuation [new file with mode: 0644]

index 0443787..0e8941c 100644 (file)
@@ -233,3 +233,16 @@ void Graphic3d_CLight::SetSmoothAngle (Standard_ShortReal theValue)
   updateRevisionIf (Abs (mySmoothness - theValue) > ShortRealEpsilon());
   mySmoothness = theValue;
 }
+
+// =======================================================================
+// function : SetRange
+// purpose  :
+// =======================================================================
+void Graphic3d_CLight::SetRange (Standard_ShortReal theValue)
+{
+  Standard_ProgramError_Raise_if (myType != Graphic3d_TOLS_POSITIONAL && myType != Graphic3d_TOLS_SPOT,
+                                  "Graphic3d_CLight::SetRange(), incorrect light type");
+  Standard_OutOfRange_Raise_if (theValue < 0.0, "Graphic3d_CLight::SetRange(), Bad value for falloff range");
+  updateRevisionIf (Abs (Range() - theValue) > ShortRealEpsilon());
+  myDirection.w() = theValue;
+};
\ No newline at end of file
index fd8b68c..54276d7 100644 (file)
@@ -177,6 +177,16 @@ public:
   //! Modifies the smoothing angle (in radians) of directional light source; should be within range [0.0, M_PI/2].
   Standard_EXPORT void SetSmoothAngle (Standard_ShortReal theValue);
 
+  //! Returns maximum distance on which point light source affects to objects and is considered during illumination calculations.
+  //! 0.0 means disabling range considering at all without any distance limits.
+  //! Has sense only for point light sources (positional and spot).  
+  Standard_ShortReal Range() const { return myDirection.w(); }
+
+  //! Modifies maximum distance on which point light source affects to objects and is considered during illumination calculations.
+  //! Positional and spot lights are only point light sources.
+  //! 0.0 means disabling range considering at all without any distance limits.
+  Standard_EXPORT void SetRange (Standard_ShortReal theValue);
+
 //! @name low-level access methods
 public:
 
@@ -189,8 +199,8 @@ public:
   //! Returns the color of the light source with dummy Alpha component, which should be ignored.
   const Graphic3d_Vec4& PackedColor() const { return myColor; }
 
-  //! Returns direction of directional/spot light.
-  const Graphic3d_Vec4& PackedDirection() const { return myDirection; }
+  //! Returns direction of directional/spot light and range for positional/spot light in alpha channel.
+  const Graphic3d_Vec4& PackedDirectionRange() const { return myDirection; }
 
   //! @return modification counter
   Standard_Size Revision() const { return myRevision; }
@@ -204,10 +214,10 @@ private:
   Standard_ShortReal& changeLinearAttenuation() { return myParams.y(); }
 
   //! Access spotlight angle parameter from packed vector.
-  Standard_ShortReal& changeAngle()             { return myParams.z();  }
+  Standard_ShortReal& changeAngle()             { return myParams.z(); }
 
   //! Access spotlight concentration parameter from packed vector.
-  Standard_ShortReal& changeConcentration()     { return myParams.w();  }
+  Standard_ShortReal& changeConcentration()     { return myParams.w(); }
 
 private:
 
index a1dfa2b..bb3c407 100644 (file)
@@ -34,6 +34,7 @@
 #include "../Shaders/Shaders_PBRIllumination_glsl.pxx"
 #include "../Shaders/Shaders_PBREnvBaking_fs.pxx"
 #include "../Shaders/Shaders_PBREnvBaking_vs.pxx"
+#include "../Shaders/Shaders_PointLightAttenuation_glsl.pxx"
 
 IMPLEMENT_STANDARD_RTTIEXT(OpenGl_ShaderManager,Standard_Transient)
 
@@ -117,9 +118,10 @@ const char THE_FUNC_pointLight[] =
   EOL"  aLight -= thePoint;"
   EOL
   EOL"  float aDist = length (aLight);"
-  EOL"  aLight = aLight * (1.0 / aDist);"
-  EOL
-  EOL"  float anAtten = 1.0 / (occLight_ConstAttenuation (theId) + occLight_LinearAttenuation (theId) * aDist);"
+  EOL"  float aRange = occLight_Range (theId);"
+  EOL"  float anAtten = occPointLightAttenuation (aDist, aRange, occLight_LinearAttenuation (theId), occLight_ConstAttenuation (theId));"
+  EOL"  if (anAtten <= 0.0) return;"
+  EOL"  aLight /= aDist;"
   EOL
   EOL"  vec3 aHalf = normalize (aLight + theView);"
   EOL
@@ -153,10 +155,11 @@ const char THE_FUNC_PBR_pointLight[] =
   EOL"  aLight -= thePoint;"
   EOL
   EOL"  float aDist = length (aLight);"
+  EOL"  float aRange = occLight_Range (theId);"
+  EOL"  float anAtten = occPointLightAttenuation (aDist, aRange);"
+  EOL"  if (anAtten <= 0.0) return;"
   EOL"  aLight /= aDist;"
   EOL
-  EOL"  float anAtten = 1.0 / max (aDist * aDist, 0.01);"
-  EOL
   EOL"  theNormal = theIsFront ? theNormal : -theNormal;"
   EOL"  DirectLighting += occPBRIllumination (theView, aLight, theNormal,"
   EOL"                                        BaseColor, Metallic, Roughness, IOR,"
@@ -182,7 +185,10 @@ const char THE_FUNC_spotLight[] =
   EOL"  aLight -= thePoint;"
   EOL
   EOL"  float aDist = length (aLight);"
-  EOL"  aLight = aLight * (1.0 / aDist);"
+  EOL"  float aRange = occLight_Range (theId);"
+  EOL"  float anAtten = occPointLightAttenuation (aDist, aRange, occLight_LinearAttenuation (theId), occLight_ConstAttenuation (theId));"
+  EOL"  if (anAtten <= 0.0) return;"
+  EOL"  aLight /= aDist;"
   EOL
   EOL"  aSpotDir = normalize (aSpotDir);"
   // light cone
@@ -193,8 +199,6 @@ const char THE_FUNC_spotLight[] =
   EOL"  }"
   EOL
   EOL"  float anExponent = occLight_SpotExponent (theId);"
-  EOL"  float anAtten    = 1.0 / (occLight_ConstAttenuation  (theId)"
-  EOL"                          + occLight_LinearAttenuation (theId) * aDist);"
   EOL"  if (anExponent > 0.0)"
   EOL"  {"
   EOL"    anAtten *= pow (aCosA, anExponent * 128.0);"
@@ -234,25 +238,30 @@ const char THE_FUNC_spotLight[] =
   EOL"  aLight -= thePoint;"
   EOL
   EOL"  float aDist = length (aLight);"
+  EOL"  float aRange = occLight_Range (theId);"
+  EOL"  float anAtten = occPointLightAttenuation (aDist, aRange);"
+  EOL"  if (anAtten <= 0.0) return;"
   EOL"  aLight /= aDist;"
   EOL
   EOL"  aSpotDir = normalize (aSpotDir);"
   // light cone
   EOL"  float aCosA = dot (aSpotDir, -aLight);"
-  EOL"  if (aCosA >= 1.0 || aCosA < cos (occLight_SpotCutOff (theId)))"
+  EOL"  float aRelativeAngle = 2.0 * acos(aCosA) / occLight_SpotCutOff(theId);"
+  EOL"  if (aCosA >= 1.0 || aRelativeAngle > 1.0)"
   EOL"  {"
   EOL"    return;"
   EOL"  }"
-  EOL
   EOL"  float anExponent = occLight_SpotExponent (theId);"
-  EOL"  float anAtten    = 1.0 / max (aDist * aDist, 0.01);"
-  EOL"  if (anExponent > 0.0)"
+  EOL"  if ((1.0 - aRelativeAngle) <= anExponent)"
   EOL"  {"
-  EOL"    anAtten *= pow (aCosA, anExponent * 128.0);"
+  EOL"    float anAngularAttenuationOffset = cos(0.5 * occLight_SpotCutOff(theId));"
+  EOL"    float anAngularAttenuationScale = 1.0 / max(0.001, cos(0.5 * occLight_SpotCutOff(theId) * (1.0 - anExponent)) - anAngularAttenuationOffset);"
+  EOL"    anAngularAttenuationOffset *= -anAngularAttenuationScale;"
+  EOL"    float anAngularAttenuantion = clamp(aCosA * anAngularAttenuationScale + anAngularAttenuationOffset, 0.0, 1.0);"
+  EOL"    anAtten *= anAngularAttenuantion * anAngularAttenuantion;"
   EOL"  }"
-  EOL
   EOL"  theNormal = theIsFront ? theNormal : -theNormal;"
-  EOL"  DirectLighting += occPBRIllumination (theView aLight, theNormal,"
+  EOL"  DirectLighting += occPBRIllumination (theView, aLight, theNormal,"
   EOL"                                        BaseColor, Metallic, Roughness, IOR,"
   EOL"                                        occLight_Specular(theId),"
   EOL"                                        occLight_Intensity(theId) * anAtten);"
@@ -459,7 +468,7 @@ EOL"  gl_Position = occProjectionMatrix * occWorldViewMatrix * occModelWorldMatr
       case Graphic3d_TOLS_DIRECTIONAL:
       {
         // if the last parameter of GL_POSITION, is zero, the corresponding light source is a Directional one
-        const OpenGl_Vec4 anInfDir = -theLight.PackedDirection();
+        const OpenGl_Vec4 anInfDir = -theLight.PackedDirectionRange();
 
         // to create a realistic effect,  set the GL_SPECULAR parameter to the same value as the GL_DIFFUSE.
         theCtx->core11->glLightfv (theLightGlId, GL_AMBIENT,               THE_DEFAULT_AMBIENT);
@@ -494,7 +503,7 @@ EOL"  gl_Position = occProjectionMatrix * occWorldViewMatrix * occModelWorldMatr
         theCtx->core11->glLightfv (theLightGlId, GL_DIFFUSE,               aLightColor.GetData());
         theCtx->core11->glLightfv (theLightGlId, GL_SPECULAR,              aLightColor.GetData());
         theCtx->core11->glLightfv (theLightGlId, GL_POSITION,              aPosition.GetData());
-        theCtx->core11->glLightfv (theLightGlId, GL_SPOT_DIRECTION,        theLight.PackedDirection().GetData());
+        theCtx->core11->glLightfv (theLightGlId, GL_SPOT_DIRECTION,        theLight.PackedDirectionRange().GetData());
         theCtx->core11->glLightf  (theLightGlId, GL_SPOT_EXPONENT,         theLight.Concentration() * 128.0f);
         theCtx->core11->glLightf  (theLightGlId, GL_SPOT_CUTOFF,          (theLight.Angle() * 180.0f) / GLfloat(M_PI));
         theCtx->core11->glLightf  (theLightGlId, GL_CONSTANT_ATTENUATION,  theLight.ConstAttenuation());
@@ -903,7 +912,7 @@ void OpenGl_ShaderManager::pushLightSourceState (const Handle(OpenGl_ShaderProgr
     aLightParams.Color.a() = aLight.Intensity(); // used by PBR and ignored by old shading model
     if (aLight.Type() == Graphic3d_TOLS_DIRECTIONAL)
     {
-      aLightParams.Position = -aLight.PackedDirection();
+      aLightParams.Position = -aLight.PackedDirectionRange();
     }
     else if (!aLight.IsHeadlight())
     {
@@ -921,7 +930,11 @@ void OpenGl_ShaderManager::pushLightSourceState (const Handle(OpenGl_ShaderProgr
 
     if (aLight.Type() == Graphic3d_TOLS_SPOT)
     {
-      aLightParams.Direction = aLight.PackedDirection();
+      aLightParams.Direction = aLight.PackedDirectionRange();
+    }
+    if (aLight.Type() == Graphic3d_TOLS_POSITIONAL)
+    {
+      aLightParams.Direction.w() = aLight.Range();
     }
     aLightParams.Parameters = aLight.PackedParams();
     ++aLightsNb;
@@ -2325,6 +2338,7 @@ TCollection_AsciiString OpenGl_ShaderManager::stdComputeLighting (Standard_Integ
   {
     return TCollection_AsciiString()
     + THE_FUNC_lightDef
+    + Shaders_PointLightAttenuation_glsl
     + aLightsFunc
     + EOL
       EOL"vec4 computeLighting (in vec3 theNormal,"
@@ -2352,6 +2366,7 @@ TCollection_AsciiString OpenGl_ShaderManager::stdComputeLighting (Standard_Integ
   {
     return TCollection_AsciiString()
     + THE_FUNC_PBR_lightDef
+    + Shaders_PointLightAttenuation_glsl
     + aLightsFunc
     + EOL
       EOL"vec4 computeLighting (in vec3 theNormal,"
index e11f179..7089b5c 100644 (file)
@@ -762,7 +762,7 @@ protected:
   {
     OpenGl_Vec4 Color;      //!< RGB color + Intensity (in .w)
     OpenGl_Vec4 Position;   //!< XYZ Direction or Position + IsHeadlight (in .w)
-    OpenGl_Vec4 Direction;  //!< spot light XYZ direction
+    OpenGl_Vec4 Direction;  //!< spot light XYZ direction + Range (in .w)
     OpenGl_Vec4 Parameters; //!< same as Graphic3d_CLight::PackedParams()
 
     //! Returns packed (serialized) representation of light source properties
index 266a82a..90377c6 100644 (file)
@@ -2440,9 +2440,9 @@ Standard_Boolean OpenGl_View::updateRaytraceLightSources (const OpenGl_Mat4& the
                           aLightColor.b() * aLight.Intensity(),
                           1.0f);
 
-    BVH_Vec4f aPosition (-aLight.PackedDirection().x(),
-                         -aLight.PackedDirection().y(),
-                         -aLight.PackedDirection().z(),
+    BVH_Vec4f aPosition (-aLight.PackedDirectionRange().x(),
+                         -aLight.PackedDirectionRange().y(),
+                         -aLight.PackedDirectionRange().z(),
                          0.0f);
 
     if (aLight.Type() != Graphic3d_TOLS_DIRECTIONAL)
index b7dbe78..081fa70 100644 (file)
@@ -156,6 +156,9 @@ uniform THE_PREC_ENUM int  occLightSourcesCount; //!< Total number of light sour
 //! Direction of specified spot light source, vec3.
 #define occLight_SpotDirection(theId)     occLightSources[theId * 4 + 2].xyz
 
+//! Range on which point light source (positional or spot) can affect (>= 0), float.
+#define occLight_Range(theId)             occLightSources[theId * 4 + 2].w
+
 //! Maximum spread angle of the spot light (in radians), float.
 #define occLight_SpotCutOff(theId)        occLightSources[theId * 4 + 3].z
 
index 327206a..06d166b 100644 (file)
@@ -9,6 +9,7 @@ srcinc:::PBRGeometry.glsl
 srcinc:::PBRIllumination.glsl
 srcinc:::PhongShading.fs
 srcinc:::PhongShading.vs
+srcinc:::PointLightAttenuation.glsl
 srcinc:::Display.fs
 srcinc:::RaytraceBase.fs
 srcinc:::RaytraceRender.fs
@@ -25,6 +26,7 @@ Shaders_PBREnvBaking_vs.pxx
 Shaders_PBRFresnel_glsl.pxx
 Shaders_PBRGeometry_glsl.pxx
 Shaders_PBRIllumination_glsl.pxx
+Shaders_PointLightAttenuation_glsl.pxx
 Shaders_RaytraceBase_fs.pxx
 Shaders_RaytraceRender_fs.pxx
 Shaders_PathtraceBase_fs.pxx
diff --git a/src/Shaders/PointLightAttenuation.glsl b/src/Shaders/PointLightAttenuation.glsl
new file mode 100644 (file)
index 0000000..cb84039
--- /dev/null
@@ -0,0 +1,35 @@
+//! Returns point light source attenuation factor
+float occRangedPointLightAttenuation (in float theDistance, in float theRange)
+{
+  if (theDistance <= theRange)
+  {
+    float aResult = theDistance / theRange;
+    aResult *= aResult;
+    aResult *= aResult;
+    aResult = 1.0 - aResult;
+    aResult = clamp(aResult, 0.0, 1.0);
+    aResult /= max(0.0001, theDistance * theDistance);
+    return aResult;
+  }
+  return -1.0;
+}
+
+//! Returns point light source attenuation factor with quadratic attenuation in case of zero range.
+float occPointLightAttenuation (in float theDistance, in float theRange)
+{
+  if (theRange == 0.0)
+  {
+    return 1.0 / max(0.0001, theDistance * theDistance);
+  }
+  return occRangedPointLightAttenuation (theDistance, theRange);
+}
+
+//! Returns point light source attenuation factor with linear attenuation in case of zero range.
+float occPointLightAttenuation (in float theDistance, in float theRange, in float theLinearAttenuation, in float theConstAttenuation)
+{
+  if (theRange == 0.0)
+  {
+    return 1.0 / (theConstAttenuation + theLinearAttenuation * theDistance);
+  }
+  return occRangedPointLightAttenuation (theDistance, theRange);
+}
\ No newline at end of file
index 4cd83b4..cffec84 100644 (file)
@@ -159,6 +159,9 @@ static const char Shaders_Declarations_glsl[] =
   "//! Direction of specified spot light source, vec3.\n"
   "#define occLight_SpotDirection(theId)     occLightSources[theId * 4 + 2].xyz\n"
   "\n"
+  "//! Range on which point light source (positional or spot) can affect (>= 0), float.\n"
+  "#define occLight_Range(theId)             occLightSources[theId * 4 + 2].w\n"
+  "\n"
   "//! Maximum spread angle of the spot light (in radians), float.\n"
   "#define occLight_SpotCutOff(theId)        occLightSources[theId * 4 + 3].z\n"
   "\n"
diff --git a/src/Shaders/Shaders_PointLightAttenuation_glsl.pxx b/src/Shaders/Shaders_PointLightAttenuation_glsl.pxx
new file mode 100644 (file)
index 0000000..9e3e35f
--- /dev/null
@@ -0,0 +1,38 @@
+// This file has been automatically generated from resource file src/Shaders/PointLightAttenuation.glsl
+
+static const char Shaders_PointLightAttenuation_glsl[] =
+  "//! Returns point light source attenuation factor\n"
+  "float occRangedPointLightAttenuation (in float theDistance, in float theRange)\n"
+  "{\n"
+  "  if (theDistance <= theRange)\n"
+  "  {\n"
+  "    float aResult = theDistance / theRange;\n"
+  "    aResult *= aResult;\n"
+  "    aResult *= aResult;\n"
+  "    aResult = 1.0 - aResult;\n"
+  "    aResult = clamp(aResult, 0.0, 1.0);\n"
+  "    aResult /= max(0.0001, theDistance * theDistance);\n"
+  "    return aResult;\n"
+  "  }\n"
+  "  return -1.0;\n"
+  "}\n"
+  "\n"
+  "//! Returns point light source attenuation factor with quadratic attenuation in case of zero range.\n"
+  "float occPointLightAttenuation (in float theDistance, in float theRange)\n"
+  "{\n"
+  "  if (theRange == 0.0)\n"
+  "  {\n"
+  "    return 1.0 / max(0.0001, theDistance * theDistance);\n"
+  "  }\n"
+  "  return occRangedPointLightAttenuation (theDistance, theRange);\n"
+  "}\n"
+  "\n"
+  "//! Returns point light source attenuation factor with linear attenuation in case of zero range.\n"
+  "float occPointLightAttenuation (in float theDistance, in float theRange, in float theLinearAttenuation, in float theConstAttenuation)\n"
+  "{\n"
+  "  if (theRange == 0.0)\n"
+  "  {\n"
+  "    return 1.0 / (theConstAttenuation + theLinearAttenuation * theDistance);\n"
+  "  }\n"
+  "  return occRangedPointLightAttenuation (theDistance, theRange);\n"
+  "}\n";
index c1e1e05..65a1834 100644 (file)
@@ -10712,6 +10712,7 @@ static int VLight (Draw_Interpretor& theDi,
           theDi << "  Position:   " << anXYZ[0] << ", " << anXYZ[1] << ", " << anXYZ[2] << "\n";
           aLight->Attenuation (anAtten[0], anAtten[1]);
           theDi << "  Atten.:     " << anAtten[0] << " " << anAtten[1] << "\n";
+          theDi << "  Range:      " << aLight->Range() << "\n";
           break;
         }
         case V3d_SPOT:
@@ -10727,6 +10728,7 @@ static int VLight (Draw_Interpretor& theDi,
           theDi << "  Atten.:     " << anAtten[0] << " " << anAtten[1] << "\n";
           theDi << "  Angle:      " << (aLight->Angle() * 180.0 / M_PI) << "\n";
           theDi << "  Exponent:   " << aLight->Concentration() << "\n";
+          theDi << "  Range:      " << aLight->Range() << "\n";
           break;
         }
         default:
@@ -11184,6 +11186,20 @@ static int VLight (Draw_Interpretor& theDi,
 
       aLightCurr->SetConcentration ((Standard_ShortReal )Atof (theArgVec[anArgIt]));
     }
+    else if (anArgCase.IsEqual("RANGE")
+          || anArgCase.IsEqual("-RANGE"))
+    {
+      if (++anArgIt >= theArgsNb
+       || aLightCurr.IsNull()
+       || aLightCurr->Type() == Graphic3d_TOLS_AMBIENT
+       || aLightCurr->Type() == Graphic3d_TOLS_DIRECTIONAL)
+      {
+        std::cerr << "Wrong syntax at argument '" << anArg << "'!\n";
+        return 1;
+      }
+
+      aLightCurr->SetRange ((Standard_ShortReal)Atof (theArgVec[anArgIt]));
+    }
     else if (anArgCase.IsEqual ("HEAD")
           || anArgCase.IsEqual ("HEADLIGHT")
           || anArgCase.IsEqual ("-HEAD")
@@ -14382,6 +14398,7 @@ void ViewerTest::ViewerCommands(Draw_Interpretor& theCommands)
     "\n        -{linearAtten}uation value"
     "\n        -angle angleDeg"
     "\n        -{spotexp}onent value"
+    "\n        -range value"
     "\n        -local|-global"
     "\n\n        example: vlight -add positional -head 1 -pos 0 1 1 -color red"
     "\n        example: vlight -change 0 -direction 0 -1 0 -linearAttenuation 0.2",
diff --git a/tests/v3d/glsl/pbr_attenuation b/tests/v3d/glsl/pbr_attenuation
new file mode 100644 (file)
index 0000000..4d0db05
--- /dev/null
@@ -0,0 +1,69 @@
+puts "========"
+puts "0031099: Visualization, TKOpenGl - support Point light source with artistic full cut-off distance"
+puts "Test of point light's (positional and spot) attenuation and range in PBR"
+puts "========"
+
+pload XDE OCAF VISUALIZATION MODELING
+catch { Close D }
+
+vclear
+vclose ALL
+vinit View1 -w 512 -h 512
+
+vtop
+vrenderparams -shadingmodel pbr
+
+box b -2 -2 -0.00001 4 4 0.00001
+XNewDoc D
+set bs [XAddShape D b 0]
+XAddVisMaterial D m -baseColor WHITE -transparency 0.0 -metallic 0.0 -roughness 1.0
+XSetVisMaterial D $bs m
+XDisplay -dispMode 1 D
+vfit
+
+vlight -clear
+vlight -add positional -pos -1 0 1 -intensity 0.5
+vlight -add spot -pos 1 0 1 -dir 0 0 -1 -intensity 0.5 -angle 100
+
+vdump $::imagedir/${::casename}_norange_high.png
+
+vlight -change 0 -pos -1 0 0.5
+vlight -change 1 -pos 1 0 0.5
+
+vdump $::imagedir/${::casename}_norange_low.png
+
+vlight -change 0 -range 1
+vlight -change 1 -range 1
+
+vdump $::imagedir/${::casename}_range1.png
+
+vlight -change 0 -range 0.5
+vlight -change 1 -range 0.5
+
+vdump $::imagedir/${::casename}_range0_5.png
+
+vlight -change 0 -range 0.6
+vlight -change 1 -range 0.6
+
+vdump $::imagedir/${::casename}_range0_6.png
+
+vlight -change 0 -range 10
+vlight -change 1 -range 10
+
+vdump $::imagedir/${::casename}_range10.png
+
+box b -5.0 -5.0 -0.00001 10 10 0.00001
+XNewDoc D
+set bs [XAddShape D b 0]
+XAddVisMaterial D m -baseColor WHITE -transparency 0.0 -metallic 0.0 -roughness 1.0
+XSetVisMaterial D $bs m
+XDisplay -dispMode 1 D
+vfit
+vlight -clear
+for {set i 0} {$i < 5} {incr i} {
+  for {set j 0} {$j < 5} {incr j} {
+    vlight -add spot -pos [expr -4.0+2.0*$i] [expr -4.0+2.0*$j] 0.5 -dir 0 0 -1 -intensity 0.5 -angle 100 -exp [expr ($i*5+$j) / 24.0]
+  }
+}
+
+vdump $::imagedir/${::casename}_angle_attenuation.png