diff --git a/src/Graphic3d/Graphic3d_CLight.cxx b/src/Graphic3d/Graphic3d_CLight.cxx index 04437873ee..0e8941cd2d 100644 --- a/src/Graphic3d/Graphic3d_CLight.cxx +++ b/src/Graphic3d/Graphic3d_CLight.cxx @@ -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 diff --git a/src/Graphic3d/Graphic3d_CLight.hxx b/src/Graphic3d/Graphic3d_CLight.hxx index fd8b68c66d..54276d7cc2 100644 --- a/src/Graphic3d/Graphic3d_CLight.hxx +++ b/src/Graphic3d/Graphic3d_CLight.hxx @@ -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: diff --git a/src/OpenGl/OpenGl_ShaderManager.cxx b/src/OpenGl/OpenGl_ShaderManager.cxx index a1dfa2bf5f..bb3c407c51 100644 --- a/src/OpenGl/OpenGl_ShaderManager.cxx +++ b/src/OpenGl/OpenGl_ShaderManager.cxx @@ -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," diff --git a/src/OpenGl/OpenGl_ShaderManager.hxx b/src/OpenGl/OpenGl_ShaderManager.hxx index e11f179f9c..7089b5c280 100644 --- a/src/OpenGl/OpenGl_ShaderManager.hxx +++ b/src/OpenGl/OpenGl_ShaderManager.hxx @@ -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 diff --git a/src/OpenGl/OpenGl_View_Raytrace.cxx b/src/OpenGl/OpenGl_View_Raytrace.cxx index 266a82a99a..90377c68fb 100644 --- a/src/OpenGl/OpenGl_View_Raytrace.cxx +++ b/src/OpenGl/OpenGl_View_Raytrace.cxx @@ -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) diff --git a/src/Shaders/Declarations.glsl b/src/Shaders/Declarations.glsl index b7dbe78c2e..081fa70e7f 100644 --- a/src/Shaders/Declarations.glsl +++ b/src/Shaders/Declarations.glsl @@ -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 diff --git a/src/Shaders/FILES b/src/Shaders/FILES index 327206af37..06d166ba2e 100644 --- a/src/Shaders/FILES +++ b/src/Shaders/FILES @@ -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 index 0000000000..cb840393a5 --- /dev/null +++ b/src/Shaders/PointLightAttenuation.glsl @@ -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 diff --git a/src/Shaders/Shaders_Declarations_glsl.pxx b/src/Shaders/Shaders_Declarations_glsl.pxx index 4cd83b435d..cffec84091 100644 --- a/src/Shaders/Shaders_Declarations_glsl.pxx +++ b/src/Shaders/Shaders_Declarations_glsl.pxx @@ -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 index 0000000000..9e3e35f314 --- /dev/null +++ b/src/Shaders/Shaders_PointLightAttenuation_glsl.pxx @@ -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"; diff --git a/src/ViewerTest/ViewerTest_ViewerCommands.cxx b/src/ViewerTest/ViewerTest_ViewerCommands.cxx index c1e1e059d5..65a1834d49 100644 --- a/src/ViewerTest/ViewerTest_ViewerCommands.cxx +++ b/src/ViewerTest/ViewerTest_ViewerCommands.cxx @@ -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 index 0000000000..4d0db059f1 --- /dev/null +++ b/tests/v3d/glsl/pbr_attenuation @@ -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