| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- -- This demo renders a scene with several uses of stencil maps (skip down to comment "Stencils here")
- --
- -- Sample contributed by andi mcc
- local scene = {}
- function scene.load()
- -- So we can see the effects of the stencils, we want to put some things in our scene.
- -- A checkerboard floor:
- scene.floorSize = 6
- -- A series of sideways-drifting cubes (these will be stenciled)
- scene.driftCubeCount = 60
- scene.boundMin = vector(-10, -1, -10)
- scene.boundMax = vector( 10, 9, 10)
- scene.speed = 1
- scene.driftCubeSize = 0.6
- scene.driftCubes = {}
- for i = 1, scene.driftCubeCount do
- scene.generateDriftCube(i, true)
- end
- -- A 3x3 cube made of two different stencil types
- scene.stencilCubeCenter = vector(0, 1.5, -0.5)
- scene.stencilCubeSize = 0.25
- scene.stencilCubeRotate = 0
- scene.stencilCubeRotateSpeed = 1
- scene.stencilCubes = {}
- for z = -1, 1 do
- for y = -1, 1 do
- for x = -1, 1 do -- Iterate over every cube
- if not (x==0 and y==0 and z==0) then -- Except the center
- table.insert(scene.stencilCubes, { vector(x,y,z), math.random(1,2) }) -- Cube center and stencil type
- end
- end
- end
- end
- -- Three cubemap skyboxes, of different colors
- scene.skybox = {}
- local skyboxTextureSize = 32
- local bandSize=3
- for cube_index,colors in ipairs{
- {{ 1, 0.5, 1 }, { 1, 1, 1 }}, -- Fuschia and white
- {{ 1, 1, 0.5 }, { 0, 0, 0 }}, -- Yellow and black
- {{ 1, 1, 1 }, { 0.9, 0.9, 0.9 }}, -- White and silver
- } do
- local layers = {}
- for layer = 1, 6 do -- 6 layers to a cubemap
- local data = lovr.data.newImage(skyboxTextureSize, skyboxTextureSize, 'rgba8')
- for y = 1, skyboxTextureSize do
- for x = 1, skyboxTextureSize do
- local isBorder = x == 1 or x == skyboxTextureSize or y == 1 or y == skyboxTextureSize -- Solid color in corners
- local direction = cube_index==3 and -1 or 1 -- Reverse direction on third cubemap
- local whichColor = (isBorder or ((x + direction * y - 2) % (bandSize * 2) >= bandSize)) and 1 or 2 -- Diagonal stripes
- data:setPixel(x - 1, y - 1, unpack(colors[whichColor]))
- end
- end
- table.insert(layers, data)
- end
- table.insert(scene.skybox, lovr.graphics.newTexture(layers))
- end
- scene.sampler = lovr.graphics.newSampler({ filter = 'nearest' })
- end
- local function randomQuaternion()
- local u, v, w = math.random(), math.random(), math.random()
- return quaternion.pack(
- math.sqrt(1 - u) * math.sin(2 * v * math.pi),
- math.sqrt(1 - u) * math.cos(2 * v * math.pi),
- math.sqrt(u) * math.sin(2 * w * math.pi),
- math.sqrt(u) * math.cos(2 * w * math.pi)
- )
- end
- function scene.generateDriftCube(i, randomX) -- Generate one cube with random position and color and a random rotational velocity
- local cube = {}
- local x, y, z
- if randomX then
- x = scene.boundMin.x + math.random() * (scene.boundMax.x - scene.boundMin.x)
- else
- x = scene.boundMin.x
- end
- y = scene.boundMin.y + math.random()*(scene.boundMax.y-scene.boundMin.y)
- z = scene.boundMin.z + math.random()*(scene.boundMax.z-scene.boundMin.z)
- cube.at = vector(x, y, z)
- cube.rotateBasis = randomQuaternion()
- cube.rotateTarget = cube.rotateBasis:conjugate()
- cube.rotate = cube.rotateBasis
- scene.driftCubes[i] = cube
- end
- function scene.update(dt) -- On each frame, move each cube and spin it a little
- for i, cube in ipairs(scene.driftCubes) do
- cube.at = cube.at + vector(scene.speed * dt, 0, 0)
- if cube.at.x > scene.boundMax.x then -- If cube left the scene bounds respawn it
- scene.generateDriftCube(i)
- else
- local rotateAmount = (cube.at.x - scene.boundMin.x) / (scene.boundMax.x - scene.boundMin.x)
- cube.rotate = cube.rotateBasis:slerp(cube.rotateTarget, rotateAmount)
- end
- end
- -- Also rotate the center cube
- scene.stencilCubeRotate = scene.stencilCubeRotate + dt * scene.stencilCubeRotateSpeed
- end
- function scene.draw(pass)
- -- Drawing without culling can make stencils or transparency look weird. We'll be using both...
- pass:setCullMode('back')
- -- First, draw the skybox
- pass:setSampler(scene.sampler)
- pass:skybox(scene.skybox[3])
- -- Next, draw a floor
- local floorRecenter = scene.floorSize / 2 + 0.5
- for x = 1, scene.floorSize do
- for y = 1, scene.floorSize do
- if (x + y) % 2 == 0 then
- pass:setColor(0.25, 0.25, 0.25)
- else
- pass:setColor(0.35, 0.35, 0.35)
- end
- pass:plane(x - floorRecenter, 0, y - floorRecenter, 1, 1, -math.pi / 2, 1, 0, 0) -- Face up
- end
- end
- pass:setColor(1, 1, 1, 1)
- -- Stencils here
- -- Using stencils involves drawing twice, once with a stencil write set and once with a stencil test set.
- -- Example 1: Using stencils to "paint" scenes
- -- Each sub-cube in our 3x3 cube will write a different value to the stencil buffer, 1 or 2.
- pass:setColorWrite() -- In the color spectrum, these cubes are completely invisible! They write only stencil and depth.
- pass:push() -- Position ourselves in the right place
- pass:translate(scene.stencilCubeCenter)
- pass:rotate(scene.stencilCubeRotate, 0,1,0)
- for _, cube in ipairs(scene.stencilCubes) do
- local center, stencilValue = unpack(cube)
- -- Draw to stencil (but only when we pass the depth test)
- pass:setStencilWrite({'keep', 'keep', 'replace'}, stencilValue)
- pass:cube(center * scene.stencilCubeSize, scene.stencilCubeSize)
- end
- pass:pop()
- pass:setStencilWrite() -- Reset stencil write
- pass:setColorWrite(true) -- Reset color write
- -- Now that we've painted the stencil buffer, let's draw something with depth, like a skybox
- pass:setDepthTest() -- Turn off depth test because the skybox is "behind" the cubes
- for stencilValue = 1, 2 do
- pass:setStencilTest('equal', stencilValue) -- Commands after here will only draw on pixels where the stencil value is right
- pass:skybox(scene.skybox[stencilValue])
- end
- pass:setDepthTest('gequal') -- Turn depth test back on
- -- Example 2: Using stencils to prevent collision
- -- 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,
- -- But we don't want any cubes to overlap each other. We want each cube to look like a "world of shadow".
- -- The cubes can darken the skybox and the 3x3 cube, but not any pixel where another cube has already drawn.
- pass:setStencilTest('notequal', 3) -- We will write the value "3", but refuse to write any pixel where a 3 is already present.
- pass:setStencilWrite('replace', 3) -- Note we haven't cleared the stencil buffer, so we can't reuse values 1 or 2.
- for _, cube in ipairs(scene.driftCubes) do
- pass:setColor(0.75, 0.5, 0.5, 0.5)
- pass:cube(cube.at, scene.driftCubeSize, cube.rotate)
- end
- -- The stencil state will reset at the end of this lovr.draw, but let's clear it anyway.
- pass:setStencilWrite()
- pass:setStencilTest()
- end
- -- Handle lovr
- function lovr.load()
- lovr.graphics.setBackgroundColor(1, 0, 0) -- Red to show up clearly if something goes wrong
- scene.load()
- end
- function lovr.update(dt)
- scene.update(dt)
- end
- function lovr.draw(pass)
- scene.draw(pass)
- end
|