-- This demo renders a scene into a cubemap, then displays the rendered screen reflected on a sphere surface within the screen. -- -- Sample contributed by andi mcc with help from holo -- First a simple scene, a checkerboard floor and some floating cubes -- Want to see how the cubemap is done? Skip this whole section local scene = {} function scene.load() scene.floorSize = 6 scene.cubeCount = 60 scene.boundMin = lovr.math.newVec3(-10, -1, -10) scene.boundMax = lovr.math.newVec3(10, 9, 10) scene.speed = 1 scene.rotateSpeed = 1 scene.cubeSize = 0.2 scene.cubes = {} scene.sphereCenter = lovr.math.newVec3(0, 1.5, -0.5) scene.sphereRad = 0.125 for i=1,scene.cubeCount do scene.generate(i, true) end end local function randomQuaternion() -- Formula from http://planning.cs.uiuc.edu/node198.html local u,v,w = math.random(), math.random(), math.random() return lovr.math.newQuat( 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), true ) -- Raw components end function scene.generate(i, randomZ) -- Generate each cube with random position and color and a random rotational velocity local cube = {} cube.at = lovr.math.newVec3() cube.at.x = scene.boundMin.x + math.random()*(scene.boundMax.x-scene.boundMin.x) cube.at.y = scene.boundMin.y + math.random()*(scene.boundMax.y-scene.boundMin.y) if randomZ then cube.at.z = scene.boundMin.z + math.random()*(scene.boundMax.z-scene.boundMin.z) else cube.at.z = scene.boundMin.z end cube.rotateBasis = randomQuaternion() cube.rotateTarget = lovr.math.newQuat(cube.rotateBasis:conjugate()) cube.rotate = cube.rotateBasis cube.color = {math.random()*0.8, math.random()*0.8, math.random()*0.8} scene.cubes[i] = cube end function scene.update(dt) -- On each frame, move each cube and spin it a little for i,cube in ipairs(scene.cubes) do cube.at.z = cube.at.z + scene.speed*dt if cube.at.z > scene.boundMax.z then -- If cube left the scene bounds respawn it scene.generate(i) else local rotateAmount = (cube.at.z - scene.boundMin.z)/(scene.boundMax.z-scene.boundMin.z) cube.rotate = cube.rotateBasis:slerp( cube.rotateTarget, rotateAmount ) end end end function scene.draw(pass) -- First, 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.5,0.5,0.5) end pass:plane(x-floorRecenter,0,y-floorRecenter, 1,1, math.pi/2,1,0,0) end end -- Draw cubes for _,cube in ipairs(scene.cubes) do pass:setColor(unpack(cube.color)) pass:cube(cube.at.x, cube.at.y, cube.at.z, scene.cubeSize, cube.rotate:unpack()) end end -- Now the cubemap stuff local cubemap = {} local unitX = lovr.math.newVec3(1,0,0) local unitY = lovr.math.newVec3(0,1,0) local unitZ = lovr.math.newVec3(0,0,1) function cubemap.load() -- Create cubemap textures local cubemapWidth, cubemapHeight = 256, 256 cubemap.texture = lovr.graphics.newTexture(cubemapWidth, cubemapHeight, 6, { type = "cube" }) cubemap.faces = {} -- Precalculate cubemap View-Projection matrices local center = scene.sphereCenter cubemap.facePerspective = lovr.math.newMat4():perspective(math.rad(90.0), 1, .1, 0) for i,matrix in ipairs{ -- Not sure why the x flip is needed! lovr.math.mat4():lookAt(center, center - unitX, vec3(0, 1, 0)), lovr.math.mat4():lookAt(center, center + unitX, vec3(0, 1, 0)), lovr.math.mat4():lookAt(center, center + unitY, vec3(0, 0, -1)), lovr.math.mat4():lookAt(center, center - unitY, vec3(0, 0, 1)), lovr.math.mat4():lookAt(center, center + unitZ, vec3(0, 1, 0)), lovr.math.mat4():lookAt(center, center - unitZ, vec3(0, 1, 0)) } do -- Each face will contain a matrix local face = {} face.matrix = lovr.math.newMat4(matrix) cubemap.faces[i] = face end -- Create reflection shader cubemap.shader = lovr.graphics.newShader('unlit', [[ uniform textureCube cubemap; vec4 lovrmain() { vec3 V = normalize(CameraPositionWorld - PositionWorld); vec3 N = normalize(Normal); vec3 R = reflect(-V, N); vec4 sphereColor = Color * getPixel(cubemap, R * vec3(-1, 1, 1)); float ndi = dot(N, V) * 0.5 + 0.5; // Darken the sphere a little around the edges to give it apparent depth return vec4(sphereColor.rgb * ndi, 1.); } ]]) -- Set up a render pass that renders to the cubemap cubemap.pass = lovr.graphics.newPass(cubemap.texture) cubemap.pass:setClear(lovr.graphics.getBackgroundColor()) end function cubemap.draw() cubemap.pass:reset() for i = 1, 6 do cubemap.pass:setProjection(i, cubemap.facePerspective) cubemap.pass:setViewPose(i,cubemap.faces[i].matrix,true) end scene.draw(cubemap.pass) end -- Handle lovr function lovr.load() lovr.graphics.setBackgroundColor(0.9,0.9,0.9) scene.load() cubemap.load() end function lovr.update(dt) scene.update(dt) end function lovr.draw(pass) cubemap.draw() scene.draw(pass) -- Draw sphere textured with cube map pass:setColor(1,0.6,0.6) pass:setShader(cubemap.shader) pass:send("cubemap", cubemap.texture) pass:sphere(scene.sphereCenter.x, scene.sphereCenter.y, scene.sphereCenter.z, scene.sphereRad) return lovr.graphics.submit(cubemap.pass, pass) end