main.lua 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. --[[
  2. Example of basic shadow mapping in LÖVR
  3. Ben Porter, 2022
  4. ]]
  5. local z = -2
  6. local light_pos = lovr.math.newVec3(3.0, 4.0, z)
  7. local light_orthographic = false -- Use orthographic light
  8. local shadow_map_size = 2048
  9. local debug_render_from_light = false -- Enable to render scene from light
  10. local debug_show_shadow_map = false -- Enable to view shadow map in overlap
  11. local shader, render_texture
  12. local shadow_map_texture, shadow_map_sampler
  13. local light_space_matrix
  14. local shadow_map_pass, lighting_pass
  15. local function render_scene(pass)
  16. local t = lovr.timer.getTime()
  17. pass:push()
  18. pass:setColor(0xCBC1AD)
  19. pass:circle(0, 0, z, 5, -math.pi / 2, 1, 0, 0, 'fill', 0, 2 * math.pi, 256)
  20. local count = 2
  21. local min, max = -(count - 1) / 2, (count - 1) / 2
  22. pass:setColor(0xEEEEEE)
  23. pass:translate(0, -min + .55, z)
  24. for i = min, max do
  25. for j = min, max do
  26. for k = min, max do
  27. pass:push()
  28. pass:translate(i, j, k)
  29. pass:rotate(t + i * 0.3 + j * 0.3, 1, 1, 1)
  30. pass:scale(0.45)
  31. if i + j % 2 == 1 then
  32. pass:box()
  33. else
  34. pass:scale(0.7)
  35. pass:sphere()
  36. end
  37. pass:pop()
  38. end
  39. end
  40. end
  41. pass:pop()
  42. end
  43. local function lighting_shader()
  44. local vs = [[
  45. vec4 lovrmain() {
  46. return Projection * View * Transform * VertexPosition;
  47. }
  48. ]]
  49. local fs = [[
  50. Constants {
  51. vec3 lightPos;
  52. mat4 lightSpaceMatrix;
  53. bool lightOrthographic;
  54. };
  55. layout(set = 2, binding = 0) uniform texture2D shadowMapTexture;
  56. vec4 diffuseLighting(vec3 lightDir, vec3 normal, float shadow) {
  57. float diff = max(dot(normal, lightDir), 0.0);
  58. vec4 diffuse = diff * vec4(1.0, 1.0, 0.8, 1.0);
  59. vec4 baseColor = Color * getPixel(ColorTexture, UV);
  60. vec4 ambience = vec4(0.05, 0.05, 0.1, 1.0);
  61. return baseColor * (ambience + (1 - shadow) * diffuse);
  62. }
  63. // Falloff shadow near edge of light bounds/frustum
  64. float shadowFalloff(vec2 uv) {
  65. const float margin = 0.05;
  66. uv = clamp(uv, vec2(0,0), vec2(1,1));
  67. float dx = 1;
  68. if (uv.x < margin) dx = uv.x / margin;
  69. else if (uv.x > 1 - margin) dx = ( 1 - uv.x ) / margin;
  70. float dy = 1;
  71. if (uv.y < margin) dy = uv.y / margin;
  72. else if (uv.y > 1 - margin) dy = ( 1 - uv.y ) / margin;
  73. return dx * dy;
  74. }
  75. vec4 lovrmain() {
  76. vec3 lightDir = normalize(lightPos - PositionWorld);
  77. vec3 normal = normalize(Normal);
  78. vec4 positionLightSpace = lightSpaceMatrix * vec4(PositionWorld, 1);
  79. vec3 positionLightSpaceProj = 0.5 * (positionLightSpace.xyz / positionLightSpace.w) + 0.5;
  80. vec4 shadowMap = getPixel(shadowMapTexture, positionLightSpaceProj.xy);
  81. float closestDepth = shadowMap.r * 0.5 + 0.5;
  82. float currentDepth = positionLightSpaceProj.z;
  83. float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005);
  84. float falloff = shadowFalloff(positionLightSpaceProj.xy);
  85. float shadow;
  86. if (lightOrthographic) {
  87. shadow = ((currentDepth - bias) >= closestDepth) ? 1.0 : 0.0;
  88. } else {
  89. shadow = ((currentDepth + bias) <= closestDepth) ? 1.0 : 0.0;
  90. }
  91. return diffuseLighting(lightDir, normal, falloff * shadow);
  92. }
  93. ]]
  94. return lovr.graphics.newShader(vs, fs, {})
  95. end
  96. local function render_shadow_map(draw)
  97. local near_plane = 2
  98. local projection
  99. if light_orthographic then
  100. local radius = 3
  101. local far_plane = 15
  102. projection = mat4():orthographic(-radius, radius, -radius, radius, near_plane, far_plane)
  103. else
  104. projection = mat4():perspective(math.pi / 3, 1, near_plane)
  105. end
  106. local view = mat4():lookAt(light_pos, vec3(0, 1, z))
  107. light_space_matrix = mat4(projection):mul(view)
  108. shadow_map_pass:reset()
  109. shadow_map_pass:setProjection(1, projection)
  110. shadow_map_pass:setViewPose(1, view, true)
  111. if light_orthographic then
  112. -- Note for ortho projection with a far plane the depth coord is reversed
  113. shadow_map_pass:setDepthTest('lequal')
  114. end
  115. if debug_render_from_light then
  116. shadow_map_pass:setShader(shader)
  117. shadow_map_pass:send('lightPos', light_pos)
  118. end
  119. draw(shadow_map_pass)
  120. end
  121. local function render_lighting_pass(draw)
  122. lighting_pass:reset()
  123. if lovr.headset then
  124. for i = 1, lovr.headset.getViewCount() do
  125. lighting_pass:setViewPose(i, lovr.headset.getViewPose(i))
  126. lighting_pass:setProjection(i, lovr.headset.getViewAngles(i))
  127. end
  128. else
  129. local t = lovr.timer.getTime()
  130. lighting_pass:setViewPose(1, 0, 3 - math.sin(t * 0.1), 4, -math.pi / 8, 1, 0, 0)
  131. end
  132. lighting_pass:setShader(shader)
  133. lighting_pass:setSampler(shadow_map_sampler)
  134. lighting_pass:send('shadowMapTexture', shadow_map_texture)
  135. lighting_pass:send('lightPos', light_pos)
  136. lighting_pass:send('lightSpaceMatrix', light_space_matrix)
  137. lighting_pass:send('lightOrthographic', light_orthographic)
  138. draw(lighting_pass)
  139. lighting_pass:setShader()
  140. lighting_pass:setColor(1, 1, 1, 1)
  141. lighting_pass:sphere(light_pos, 0.1)
  142. end
  143. local function debug_passes(pass)
  144. pass:setDepthWrite(false)
  145. if debug_render_from_light then
  146. pass:fill(shadow_map_texture)
  147. else
  148. pass:fill(render_texture)
  149. if debug_show_shadow_map then
  150. -- Render shadow map in overlay
  151. local width, height = lovr.system.getWindowDimensions()
  152. pass:setViewport(0, 0, width / 4, height / 4)
  153. pass:fill(shadow_map_texture)
  154. end
  155. end
  156. end
  157. function lovr.load(args)
  158. shader = lighting_shader()
  159. lovr.graphics.setBackgroundColor(0x4782B3)
  160. local shadow_map_format = debug_render_from_light and 'rgba8' or 'd32f'
  161. shadow_map_texture = lovr.graphics.newTexture(shadow_map_size, shadow_map_size, {
  162. format = shadow_map_format,
  163. linear = false,
  164. mipmaps = false
  165. })
  166. shadow_map_sampler = lovr.graphics.newSampler({ wrap = 'clamp' })
  167. if lovr.headset then
  168. local width, height = lovr.headset.getDisplayDimensions()
  169. local layers = lovr.headset.getViewCount()
  170. render_texture = lovr.graphics.newTexture(width, height, layers, { mipmaps = false })
  171. else
  172. local width, height = lovr.system.getWindowDimensions()
  173. render_texture = lovr.graphics.newTexture(width, height, 1, { mipmaps = false })
  174. end
  175. if debug_render_from_light then
  176. shadow_map_pass = lovr.graphics.newPass({ shadow_map_texture, samples = 1 })
  177. else
  178. shadow_map_pass = lovr.graphics.newPass({ depth = shadow_map_texture, samples = 1 })
  179. shadow_map_pass:setClear({ depth = light_orthographic and 1 or 0 })
  180. end
  181. lighting_pass = lovr.graphics.newPass(render_texture)
  182. end
  183. function lovr.update(dt)
  184. local t = lovr.timer.getTime()
  185. light_pos.x = 3 * math.cos(t * 0.4)
  186. light_pos.z = 3 * math.sin(t * 0.4) + z
  187. end
  188. function lovr.draw(pass)
  189. render_shadow_map(render_scene)
  190. render_lighting_pass(render_scene)
  191. debug_passes(pass)
  192. return lovr.graphics.submit({ shadow_map_pass, lighting_pass, pass })
  193. end