diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,10 @@
 # Changelog for keid-render-basic
 
+## 0.1.6.0
+
+- Updated `Loader` stage to camera changes in core.
+- Import sun (directional light) functions from demos.
+
 ## 0.1.5.0
 
 - Extracted deprecated `allocateRenderpass_` into `Render.Basic.allocate`.
diff --git a/keid-render-basic.cabal b/keid-render-basic.cabal
--- a/keid-render-basic.cabal
+++ b/keid-render-basic.cabal
@@ -5,12 +5,13 @@
 -- see: https://github.com/sol/hpack
 
 name:           keid-render-basic
-version:        0.1.5.0
+version:        0.1.6.0
 synopsis:       Basic rendering programs for Keid engine.
 category:       Game Engine
+homepage:       https://keid.haskell-game.dev
 author:         IC Rainbow
 maintainer:     keid@aenor.ru
-copyright:      2021 IC Rainbow
+copyright:      2022 IC Rainbow
 license:        BSD3
 license-file:   LICENSE
 build-type:     Simple
@@ -142,7 +143,7 @@
     , distributive
     , file-embed >=0.0.10
     , geomancy
-    , keid-core >=0.1.5.0
+    , keid-core >=0.1.6.0
     , keid-geometry
     , neat-interpolation
     , resourcet
diff --git a/src/Render/DescSets/Sun.hs b/src/Render/DescSets/Sun.hs
--- a/src/Render/DescSets/Sun.hs
+++ b/src/Render/DescSets/Sun.hs
@@ -6,6 +6,19 @@
   , set0
 
   , pattern MAX_VIEWS
+
+  , Buffer
+
+  , SunInput(..)
+  , initialSunInput
+
+  , Process
+  , spawn1
+  , mkSun
+
+  , Observer
+  , newObserver1
+  , observe1
   ) where
 
 import RIO
@@ -16,20 +29,25 @@
 import Data.Vector qualified as Vector
 import Data.Vector.Storable qualified as VectorS
 import Foreign.Storable.Generic (GStorable)
-import Geomancy (Vec4, vec4)
+import Geomancy (Vec3, Vec4, vec3, vec4)
 import Geomancy.Transform (Transform)
+import Geomancy.Transform qualified as Transform
+import Geomancy.Vec4 qualified as Vec4
 import Vulkan.Core10 qualified as Vk
 import Vulkan.CStruct.Extends (SomeStruct(..))
+import Vulkan.NamedType ((:::))
 import Vulkan.Utils.Debug qualified as Debug
 import Vulkan.Zero (Zero(..))
 
+import Engine.Camera qualified as Camera
 import Engine.Types (StageRIO)
 import Engine.Vulkan.DescSets ()
 import Engine.Vulkan.Types (DsBindings, HasVulkan(..))
+import Engine.Worker qualified as Worker
 import Resource.Buffer qualified as Buffer
 import Resource.DescriptorSet qualified as DescriptorSet
 
--- * Set0 data
+-- * Set0 data for light projection
 
 -- | Maximum "guaranteed" amount for multiview passes
 pattern MAX_VIEWS :: Int
@@ -74,11 +92,13 @@
 
 -- * Setup
 
+type Buffer = Buffer.Allocated 'Buffer.Coherent Sun
+
 createSet0Ds
   :: Tagged '[Sun] Vk.DescriptorSetLayout
   -> ResourceT (StageRIO st)
       ( Tagged '[Sun] (Vector Vk.DescriptorSet)
-      , Buffer.Allocated 'Buffer.Coherent Sun
+      , Buffer
       )
 createSet0Ds (Tagged set0layout) = do
   context <- asks id
@@ -163,3 +183,96 @@
 
     writeSets =
       Vector.singleton writeSet0b0
+
+data SunInput = SunInput
+  { siColor :: Vec4
+
+  , siInclination :: Float
+  , siAzimuth     :: Float
+  , siRadius      :: Float
+  , siTarget      :: Vec3
+
+  , siDepthRange :: Float
+  , siSize       :: Float
+  , siShadowIx   :: Float
+  }
+
+initialSunInput :: SunInput
+initialSunInput = SunInput
+  { siColor = vec4 1 1 1 1
+
+  , siInclination = τ/8
+  , siAzimuth     = -τ/8
+  , siRadius      = Camera.PROJECTION_FAR / 2
+  , siTarget      = 0
+
+  , siDepthRange = Camera.PROJECTION_FAR
+  , siSize       = 512
+  , siShadowIx   = -1
+  }
+
+type Process = Worker.Cell SunInput ("bounding box" ::: Transform, Sun)
+
+spawn1 :: MonadUnliftIO m => SunInput -> m Process
+spawn1 = Worker.spawnCell mkSun
+
+mkSun :: SunInput -> ("bounding box" ::: Transform, Sun)
+mkSun SunInput{..} =
+  ( bbTransform
+  , Sun
+      { sunViewProjection = mconcat vp
+      , sunShadow         = vec4 0 0 siShadowIx siSize
+      , sunPosition       = Vec4.fromVec3 position 0
+      , sunDirection      = Vec4.fromVec3 direction 0
+      , sunColor          = siColor
+      }
+  )
+  where
+    vp =
+      [ Transform.rotateY (-siAzimuth)
+      , Transform.rotateX (-siInclination)
+
+      , Transform.translate 0 0 siRadius
+
+      -- XXX: some area beyond the near plane receives light, but not shadows
+      , Transform.scale3
+          (1 / siSize)
+          (1 / siSize)
+          (1 / siDepthRange)
+      ]
+
+    position = Transform.apply (vec3 0 0 siRadius) rotation
+
+    direction = Transform.apply (vec3 0 0 $ -1) rotation
+
+    bbTransform = mconcat
+      [ -- XXX: orient wire box "green/near -> far/red"
+        Transform.rotateX (τ/4)
+        -- XXX: the rest must be matched with VP flipped
+      , Transform.translate 0 0 0.5                 -- XXX: shift origin to the near face
+
+        -- XXX: reverse light transform
+      , Transform.scale3 siSize siSize siDepthRange -- XXX: size to projection volume
+      , Transform.translate 0 0 (-siRadius)         -- XXX: translate near face to radius
+      , rotation                                    -- XXX: apply sphere coords
+      ]
+
+    rotation = mconcat
+      [ Transform.rotateX siInclination
+      , Transform.rotateY siAzimuth
+      ]
+
+type Observer = Worker.ObserverIO (VectorS.Vector ("bounding box" ::: Transform))
+
+newObserver1 :: MonadIO m => m Observer
+newObserver1 = Worker.newObserverIO mempty
+
+observe1 :: MonadUnliftIO m => Process -> Observer -> Buffer -> m ()
+observe1 sunP sunOut sunData =
+  Worker.observeIO_ sunP sunOut \_oldBB (bb, sun) -> do
+    -- XXX: must stay the same or descsets must be updated with a new buffer
+    _same <- Buffer.updateCoherent (VectorS.singleton sun) sunData
+    pure $ VectorS.singleton bb
+
+τ :: Float
+τ = 2 * pi
diff --git a/src/Stage/Loader/Scene.hs b/src/Stage/Loader/Scene.hs
--- a/src/Stage/Loader/Scene.hs
+++ b/src/Stage/Loader/Scene.hs
@@ -5,8 +5,6 @@
 
 import RIO
 
-import Geomancy.Transform qualified as Transform
-
 import Engine.Camera qualified as Camera
 import Engine.Worker qualified as Worker
 import Render.DescSets.Set0 (Scene(..), emptyScene)
@@ -16,15 +14,15 @@
 spawn
   :: ( MonadUnliftIO m
      , Worker.HasOutput projection
-     , Worker.GetOutput projection ~ Camera.Projection
+     , Worker.GetOutput projection ~ Camera.Projection 'Camera.Orthographic
      )
   => projection
   -> m Process
 spawn = Worker.spawnMerge1 mkScene
 
-mkScene :: Camera.Projection -> Scene
+mkScene :: Camera.Projection 'Camera.Orthographic -> Scene
 mkScene Camera.Projection{..} =
   emptyScene
-    { sceneProjection    = projectionOrthoUI
-    , sceneInvProjection = Transform.inverse projectionOrthoUI -- FIXME: move to cell output
+    { sceneProjection    = projectionTransform
+    , sceneInvProjection = projectionInverse
     }
diff --git a/src/Stage/Loader/Setup.hs b/src/Stage/Loader/Setup.hs
--- a/src/Stage/Loader/Setup.hs
+++ b/src/Stage/Loader/Setup.hs
@@ -8,6 +8,7 @@
 import RIO
 
 import Control.Monad.Trans.Resource (ResourceT)
+import Engine.Camera qualified as Camera
 import Engine.StageSwitch (trySwitchStage)
 import Engine.Types (StackStage(..), Stage(..), StageSetupRIO)
 import Engine.Types qualified as Engine
@@ -164,7 +165,8 @@
   -> StageSetupRIO (Resource.ReleaseKey, RunState)
 initialRunState loadAction nextStage uiSettings =
   withPools \pools -> do
-    rsProjectionP <- Engine.getScreenP
+    (projectionKey, rsProjectionP) <- Worker.registered
+      Camera.spawnOrthoPixelsCentered
 
     (sceneUiKey, rsSceneUiP) <- Worker.registered $
       Scene.spawn rsProjectionP
@@ -187,7 +189,8 @@
 
     releaseKeys <- Resource.register $
       traverse_ @[] Resource.release
-        [ sceneUiKey
+        [ projectionKey
+        , sceneUiKey
         , quadKey
         , screenKey
         , uiKey
diff --git a/src/Stage/Loader/Types.hs b/src/Stage/Loader/Types.hs
--- a/src/Stage/Loader/Types.hs
+++ b/src/Stage/Loader/Types.hs
@@ -6,7 +6,6 @@
   , RunState(..)
   ) where
 
-import Engine.Camera qualified as Camera
 import Render.Basic qualified as Basic
 import Render.DescSets.Set0 qualified as Set0
 
@@ -24,8 +23,7 @@
   }
 
 data RunState = RunState
-  { rsProjectionP :: Camera.ProjectionProcess
-  , rsSceneUiP    :: Set0.Process
+  { rsSceneUiP    :: Set0.Process
 
   , rsUI :: UI
   }
