-- This demo renders a scene to a canvas, then renders the canvas to screen filtered through a shader.
--
-- Sample contributed by andi mcc
local applyBlur = true -- Set this to false to see the scene with no postprocessing.
-- For the fragment shader: We are going to create a separable gaussian blur.
-- A "separable" blur means we first blur horizontally, then blur vertically to get a 2D blur.
local blurShader = [[
// This one-dimensional blur filter samples five points and averages them by different amounts.
// Weights and offsets taken from http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/
// The weights for the center, one-point-out, and two-point-out samples
#define WEIGHT0 0.2270270270
#define WEIGHT1 0.3162162162
#define WEIGHT2 0.0702702703
// The distances-from-center for the samples
#define OFFSET1 1.3846153846
#define OFFSET2 3.2307692308
Constants {
// This constant will be set every draw to determine whether we are sampling horizontally or vertically.
vec2 direction;
};
// The texture to sample from.
uniform texture2DArray sourceTexture;
// lovr's shader architecture will automatically supply a main(), which will call this lovrmain() function
vec4 lovrmain() {
vec2 uvOffset = direction / Resolution.xy; // Convert the offset from pixels to UVs
vec4 color = vec4(0.0);
color += getPixel(sourceTexture, UV, ViewIndex) * WEIGHT0;
color += getPixel(sourceTexture, UV + uvOffset * OFFSET1, ViewIndex) * WEIGHT1;
color += getPixel(sourceTexture, UV - uvOffset * OFFSET1, ViewIndex) * WEIGHT1;
color += getPixel(sourceTexture, UV + uvOffset * OFFSET2, ViewIndex) * WEIGHT2;
color += getPixel(sourceTexture, UV - uvOffset * OFFSET2, ViewIndex) * WEIGHT2;
return color;
}
]]
-- The vertex and fragment shaders will be combined together into a shader program
local screenShader
-- Image of an eyechart
local eyechart
-- This table will contain two textures we will use as scratch space
local tempTexture
function lovr.load()
-- Load the eyechart image
-- Source: https://www.publicdomainpictures.net/view-image.php?image=244244&picture=eye-chart-test-vintage
-- Creative Commons 0 / Public Domain license
local texture = lovr.graphics.newTexture('eye-chart-test-vintage-cc0.jpg')
local textureWidth, textureHeight = texture:getDimensions()
eyechart = {
scale = .75,
aspect = textureHeight / textureWidth,
texture = texture
}
-- Configure the objects needed for the blur
if applyBlur then
local width, height = lovr.headset.getDisplayDimensions()
local layers = lovr.headset.getViewCount()
-- Compile the shader
screenShader = lovr.graphics.newShader('fill', blurShader)
-- Create two temporary textures
tempTexture = {
lovr.graphics.newTexture(width, height, layers, { mipmaps = false }),
lovr.graphics.newTexture(width, height, layers, { mipmaps = false })
}
-- Make a clamping sampler (clampler, get it?) to prevent blurred
-- pixels from wrapping around the edges
clampler = lovr.graphics.newSampler({ wrap = 'clamp' })
end
end
-- The scene is drawn in this callback
local function sceneDraw(pass)
-- Draw text on the left and right
for _, sign in ipairs { -1, 1 } do
pass:push()
pass:rotate(sign * math.pi / 2, 0, 1, 0)
pass:text('MOVE CLOSER', 0, 0, -10, 5)
pass:pop()
end
-- Draw the eye chart
pass:setMaterial(eyechart.texture)
pass:plane(0, 1.7, -1, eyechart.scale, eyechart.scale * eyechart.aspect)
end
-- This simple function is used to render a render pass that
-- draws one texture onto another with the blur shader
local function fullScreenDraw(source, destination, blurSize)
local pass = lovr.graphics.newPass({ destination, depth = false, samples = 1 })
pass:setSampler(clampler)
pass:setShader(screenShader)
pass:send('sourceTexture', source)
pass:send('direction', blurSize)
pass:fill()
return pass
end
function lovr.draw(pass)
if not applyBlur then
-- No-postprocessing path: Call scene-draw callback without doing anything fancy
sceneDraw(pass)
else
local passes = {}
-- Start by drawing the scene to one of our temp textures.
local scene = lovr.graphics.newPass(tempTexture[1])
-- Make the scene pass use the same cameras as the headset
for i = 1, pass:getViewCount() do
scene:setViewPose(i, pass:getViewPose(i))
scene:setProjection(i, pass:getProjection(i, mat4()))
end
sceneDraw(scene)
table.insert(passes, scene)
-- We now have the scene in a texture, which means we can apply a full-screen effect by
-- rendering the texture with a shader. However, because our blur is separable,
-- we will need to do this twice, once for horizontal blur and once for vertical.
-- We would also like to do multiple blur passes at larger and larger scales, to get a blurrier blur.
-- To achieve these many passes we will render from texture 1 into 2, and then 2 back into 1, and repeat.
table.insert(passes, fullScreenDraw(tempTexture[1], tempTexture[2], { 1, 0 }))
table.insert(passes, fullScreenDraw(tempTexture[2], tempTexture[1], { 0, 1 }))
table.insert(passes, fullScreenDraw(tempTexture[1], tempTexture[2], { 2, 0 }))
table.insert(passes, fullScreenDraw(tempTexture[2], tempTexture[1], { 0, 2 }))
table.insert(passes, fullScreenDraw(tempTexture[1], tempTexture[2], { 4, 0 }))
table.insert(passes, fullScreenDraw(tempTexture[2], tempTexture[1], { 0, 4 }))
-- Finally, draw the blurred texture to the main display
pass:fill(tempTexture[1])
table.insert(passes, pass)
return lovr.graphics.submit(passes)
end
end