Cubemap Generate

SourceEdit
-- 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 = vector(-10, -1, -10)
	scene.boundMax = vector(10,   9,  10)
	scene.speed = 1
	scene.rotateSpeed = 1
	scene.cubeSize = 0.2
	scene.cubes = {}

	scene.sphereCenter = vector(0, 1.5, -0.5)
	scene.sphereRad = 0.125

	for i = 1, scene.cubeCount do
		scene.generate(i, true)
	end
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.generate(i, randomZ) -- Generate each cube with random position and color and a random rotational velocity
	local cube = {}
	local x = scene.boundMin.x + math.random() * (scene.boundMax.x - scene.boundMin.x)
	local y = scene.boundMin.y + math.random() * (scene.boundMax.y - scene.boundMin.y)
	local z
	if randomZ then
		z = scene.boundMin.z + math.random() * (scene.boundMax.z - scene.boundMin.z)
	else
		z = scene.boundMin.z
	end
	cube.at = vector(x, y, z)
	cube.rotateBasis = randomQuaternion()
	cube.rotateTarget = 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 = cube.at + vector(0, 0, 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, scene.cubeSize, cube.rotate:unpack())
	end
end

-- Now the cubemap stuff

local cubemap = {}

local unitX = vector(1, 0, 0)
local unitY = vector(0, 1, 0)
local unitZ = vector(0, 0, 1)

function cubemap.load()
	-- Create cubemap texture
	cubemap.texture = lovr.graphics.newTexture(256, 256, 6, { type = "cube" })

	-- Precalculate cubemap View-Projection matrices
	local center = scene.sphereCenter
	cubemap.facePerspective = lovr.math.newMat4():perspective(math.rad(90), 1, .1, 0)
	cubemap.faces = {
		lovr.math.newMat4():lookAt(center, center - unitX, vec3(0, 1, 0)),
		lovr.math.newMat4():lookAt(center, center + unitX, vec3(0, 1, 0)),
		lovr.math.newMat4():lookAt(center, center + unitY, vec3(0, 0, -1)),
		lovr.math.newMat4():lookAt(center, center - unitY, vec3(0, 0, 1)),
		lovr.math.newMat4():lookAt(center, center + unitZ, vec3(0, 1, 0)),
		lovr.math.newMat4():lookAt(center, center - unitZ, vec3(0, 1, 0))
	}

	-- 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], 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, scene.sphereRad)

	return lovr.graphics.submit(cubemap.pass, pass)
end