main.lua 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. -- This demo renders a scene with several uses of stencil maps (skip down to comment "Stencils here")
  2. --
  3. -- Sample contributed by andi mcc
  4. local scene = {}
  5. function scene.load()
  6. -- So we can see the effects of the stencils, we want to put some things in our scene.
  7. -- A checkerboard floor:
  8. scene.floorSize = 6
  9. -- A series of sideways-drifting cubes (these will be stenciled)
  10. scene.driftCubeCount = 60
  11. scene.boundMin = vector(-10, -1, -10)
  12. scene.boundMax = vector( 10, 9, 10)
  13. scene.speed = 1
  14. scene.driftCubeSize = 0.6
  15. scene.driftCubes = {}
  16. for i = 1, scene.driftCubeCount do
  17. scene.generateDriftCube(i, true)
  18. end
  19. -- A 3x3 cube made of two different stencil types
  20. scene.stencilCubeCenter = vector(0, 1.5, -0.5)
  21. scene.stencilCubeSize = 0.25
  22. scene.stencilCubeRotate = 0
  23. scene.stencilCubeRotateSpeed = 1
  24. scene.stencilCubes = {}
  25. for z = -1, 1 do
  26. for y = -1, 1 do
  27. for x = -1, 1 do -- Iterate over every cube
  28. if not (x==0 and y==0 and z==0) then -- Except the center
  29. table.insert(scene.stencilCubes, { vector(x,y,z), math.random(1,2) }) -- Cube center and stencil type
  30. end
  31. end
  32. end
  33. end
  34. -- Three cubemap skyboxes, of different colors
  35. scene.skybox = {}
  36. local skyboxTextureSize = 32
  37. local bandSize=3
  38. for cube_index,colors in ipairs{
  39. {{ 1, 0.5, 1 }, { 1, 1, 1 }}, -- Fuschia and white
  40. {{ 1, 1, 0.5 }, { 0, 0, 0 }}, -- Yellow and black
  41. {{ 1, 1, 1 }, { 0.9, 0.9, 0.9 }}, -- White and silver
  42. } do
  43. local layers = {}
  44. for layer = 1, 6 do -- 6 layers to a cubemap
  45. local data = lovr.data.newImage(skyboxTextureSize, skyboxTextureSize, 'rgba8')
  46. for y = 1, skyboxTextureSize do
  47. for x = 1, skyboxTextureSize do
  48. local isBorder = x == 1 or x == skyboxTextureSize or y == 1 or y == skyboxTextureSize -- Solid color in corners
  49. local direction = cube_index==3 and -1 or 1 -- Reverse direction on third cubemap
  50. local whichColor = (isBorder or ((x + direction * y - 2) % (bandSize * 2) >= bandSize)) and 1 or 2 -- Diagonal stripes
  51. data:setPixel(x - 1, y - 1, unpack(colors[whichColor]))
  52. end
  53. end
  54. table.insert(layers, data)
  55. end
  56. table.insert(scene.skybox, lovr.graphics.newTexture(layers))
  57. end
  58. scene.sampler = lovr.graphics.newSampler({ filter = 'nearest' })
  59. end
  60. local function randomQuaternion()
  61. local u, v, w = math.random(), math.random(), math.random()
  62. return quaternion.pack(
  63. math.sqrt(1 - u) * math.sin(2 * v * math.pi),
  64. math.sqrt(1 - u) * math.cos(2 * v * math.pi),
  65. math.sqrt(u) * math.sin(2 * w * math.pi),
  66. math.sqrt(u) * math.cos(2 * w * math.pi)
  67. )
  68. end
  69. function scene.generateDriftCube(i, randomX) -- Generate one cube with random position and color and a random rotational velocity
  70. local cube = {}
  71. local x, y, z
  72. if randomX then
  73. x = scene.boundMin.x + math.random() * (scene.boundMax.x - scene.boundMin.x)
  74. else
  75. x = scene.boundMin.x
  76. end
  77. y = scene.boundMin.y + math.random()*(scene.boundMax.y-scene.boundMin.y)
  78. z = scene.boundMin.z + math.random()*(scene.boundMax.z-scene.boundMin.z)
  79. cube.at = vector(x, y, z)
  80. cube.rotateBasis = randomQuaternion()
  81. cube.rotateTarget = cube.rotateBasis:conjugate()
  82. cube.rotate = cube.rotateBasis
  83. scene.driftCubes[i] = cube
  84. end
  85. function scene.update(dt) -- On each frame, move each cube and spin it a little
  86. for i, cube in ipairs(scene.driftCubes) do
  87. cube.at = cube.at + vector(scene.speed * dt, 0, 0)
  88. if cube.at.x > scene.boundMax.x then -- If cube left the scene bounds respawn it
  89. scene.generateDriftCube(i)
  90. else
  91. local rotateAmount = (cube.at.x - scene.boundMin.x) / (scene.boundMax.x - scene.boundMin.x)
  92. cube.rotate = cube.rotateBasis:slerp(cube.rotateTarget, rotateAmount)
  93. end
  94. end
  95. -- Also rotate the center cube
  96. scene.stencilCubeRotate = scene.stencilCubeRotate + dt * scene.stencilCubeRotateSpeed
  97. end
  98. function scene.draw(pass)
  99. -- Drawing without culling can make stencils or transparency look weird. We'll be using both...
  100. pass:setCullMode('back')
  101. -- First, draw the skybox
  102. pass:setSampler(scene.sampler)
  103. pass:skybox(scene.skybox[3])
  104. -- Next, draw a floor
  105. local floorRecenter = scene.floorSize / 2 + 0.5
  106. for x = 1, scene.floorSize do
  107. for y = 1, scene.floorSize do
  108. if (x + y) % 2 == 0 then
  109. pass:setColor(0.25, 0.25, 0.25)
  110. else
  111. pass:setColor(0.35, 0.35, 0.35)
  112. end
  113. pass:plane(x - floorRecenter, 0, y - floorRecenter, 1, 1, -math.pi / 2, 1, 0, 0) -- Face up
  114. end
  115. end
  116. pass:setColor(1, 1, 1, 1)
  117. -- Stencils here
  118. -- Using stencils involves drawing twice, once with a stencil write set and once with a stencil test set.
  119. -- Example 1: Using stencils to "paint" scenes
  120. -- Each sub-cube in our 3x3 cube will write a different value to the stencil buffer, 1 or 2.
  121. pass:setColorWrite() -- In the color spectrum, these cubes are completely invisible! They write only stencil and depth.
  122. pass:push() -- Position ourselves in the right place
  123. pass:translate(scene.stencilCubeCenter)
  124. pass:rotate(scene.stencilCubeRotate, 0,1,0)
  125. for _, cube in ipairs(scene.stencilCubes) do
  126. local center, stencilValue = unpack(cube)
  127. -- Draw to stencil (but only when we pass the depth test)
  128. pass:setStencilWrite({'keep', 'keep', 'replace'}, stencilValue)
  129. pass:cube(center * scene.stencilCubeSize, scene.stencilCubeSize)
  130. end
  131. pass:pop()
  132. pass:setStencilWrite() -- Reset stencil write
  133. pass:setColorWrite(true) -- Reset color write
  134. -- Now that we've painted the stencil buffer, let's draw something with depth, like a skybox
  135. pass:setDepthTest() -- Turn off depth test because the skybox is "behind" the cubes
  136. for stencilValue = 1, 2 do
  137. pass:setStencilTest('equal', stencilValue) -- Commands after here will only draw on pixels where the stencil value is right
  138. pass:skybox(scene.skybox[stencilValue])
  139. end
  140. pass:setDepthTest('gequal') -- Turn depth test back on
  141. -- Example 2: Using stencils to prevent collision
  142. -- Here we will write AND test the stencil at the same time! In this step we want to draw a bunch of 50%-transparent cubes,
  143. -- But we don't want any cubes to overlap each other. We want each cube to look like a "world of shadow".
  144. -- The cubes can darken the skybox and the 3x3 cube, but not any pixel where another cube has already drawn.
  145. pass:setStencilTest('notequal', 3) -- We will write the value "3", but refuse to write any pixel where a 3 is already present.
  146. pass:setStencilWrite('replace', 3) -- Note we haven't cleared the stencil buffer, so we can't reuse values 1 or 2.
  147. for _, cube in ipairs(scene.driftCubes) do
  148. pass:setColor(0.75, 0.5, 0.5, 0.5)
  149. pass:cube(cube.at, scene.driftCubeSize, cube.rotate)
  150. end
  151. -- The stencil state will reset at the end of this lovr.draw, but let's clear it anyway.
  152. pass:setStencilWrite()
  153. pass:setStencilTest()
  154. end
  155. -- Handle lovr
  156. function lovr.load()
  157. lovr.graphics.setBackgroundColor(1, 0, 0) -- Red to show up clearly if something goes wrong
  158. scene.load()
  159. end
  160. function lovr.update(dt)
  161. scene.update(dt)
  162. end
  163. function lovr.draw(pass)
  164. scene.draw(pass)
  165. end