View
219
Download
0
Category
Preview:
Citation preview
Aula 16 – Pseudo3D em Jogos de Corrida
Introdução à EngenhariaENG1000
2018.1
Prof. Pedro Sampaio<psampaio@inf.puc-rio.br> 1
Pseudo3D vs 3D
2Forza Horizon 3
Top Gear 2
• Câmera fixa;• Sprites 2D;• Matemática simplificada;• Pipeline gráfico pequeno e simples.
Pseudo3D
• Câmera livre;• Modelos 3D;• Matemática robusta;• Pipeline gráfico extenso e complexo.
3D
3D Rendering
3
Operações matemáticas armazenadas em estruturas de matrizes e vetores
Pipeline gráfico com procedimentos complexos como o de Culling
3D Rendering
4
Pipeline gráfico OpenGL 4.4
Semelhança de Triângulos
5
Triângulos com lados proporcionais são semelhantes entre si
Projeção 3D -> 2D
6
Projetando um ponto do universo 3D para uma tela 2D:(x_world, y_world, z_world) -> (x_screen, y_screen)
Usando semelhança de triângulos calculamos nossa nova coordenada y (y_screen):
y_screen = y_world * dist / z_world
Podemos obter x_screen da mesma forma!
Pseudo3D - Transformações
7
Transformações para projeção dos pontos
Transformação de Escala
8
Precisamos escalar nosso ponto de acordo com a resolução e a orientação computacional de
coordenadas!
Field of View
9
Podemos configurar o campo de visão da nossa câmera alterando a distância entra a câmera e o plano 2D
Desnecessário para a maioria dos motores de corridas Pseudo3D!
Estrada - Geometria
10
Estrada – Estrutura de Dadosfunction buildRoad()
segments = {}
for n = 1, 500 do -- tamanho arbitrário para estrada (n segmentos)
seg = {
index = n, -- indíce do segmento na tabela
p1 = { -- ponto p1 do segmento
world = { z = (n-1) * segmentLength },
camera = { z = 1, y = 1, x = 1, w = 1, scale = 1},
screen = { z = 1, y = 1, x = 1, w = 1, scale = 1}
},
p2 = { -- ponto p2 do segmento
world = { z = n * segmentLength },
camera = { z = 1, y = 1, x = 1, w = 1, scale = 1},
screen = {z = 1, y = 1, x = 1, w = 1, scale = 1}
},
color = randomColorLightness(math.floor(n/rumbleLength)),
looped = false
}
table.insert(segments, seg)
end
11
function randomColorLightness(n)if n % 2 == 0 then
return COLORS.DARKelse
return COLORS.LIGHTend
end
Estrada – Estrutura de Dados
pSegment = findSegment(playerZ)["index"]
segments[pSegment + 2]["color"] = COLORS.START
segments[pSegment + 3]["color"] = COLORS.START
segments[pSegment + 4]["color"] = COLORS.START
segments[pSegment + 5]["color"] = COLORS.START
trackLength = tablelength(segments) * segmentLength
end
12
function findSegment(z)
return segments[(math.floor(z/segmentLength) % tablelength(segments)) + 1]
end
playerZ=cameraHeight*cameraDepth
Função tablelenght(t) retorna a quantidade de elementos em uma tabela “t”
Esquema de Cores
13
-- cores
skyblue = {135, 206, 235}
darkgray = {169, 169, 169}
greenmedium = {10, 111, 10}
whitesmoke = {245, 245, 245}
darkgreen = { 0, 100, 0}
white = {255, 255, 255}
-- esquema de coresCOLORS = {
SKY = skyblue,
LIGHT = { road = darkgray, grass = greenmedium, lane=whitesmoke },
DARK = { road = darkgray, grass = darkgreen },
START = { road = white, grass = greenmedium }
}
Movimentação – Updatefunction love.update(dt)
dx = dt * 5000 –- deslocamento no eixo x
if (keyLeft) then -- deslocar para esquerda
playerX = playerX - dx;
elseif (keyRight) then -- deloscar para direita
playerX = playerX + dx;
end
if (keyFaster) then -- acelerar movimento
speed = speed + 25
elseif (keySlower) then –- desacelerar movimento
speed = speed - 25
else -- desaceleração natural
if speed > 0 then -- considerando que movimento
speed = speed – 5 -- ocorre para frente e para trás
elseif speed < 0 then
speed = speed + 5
else
speed = 0
end
end14
Movimentação – Updatespeed = limit(speed, -10000, 10000) -– limita velocidade
playerX = limit(playerX, -10000, 10000) -- limita pos x
position = increase(position, dt * speed, trackLength)
end
15
function limit(value, minValue, maxValue)
return math.max(minValue, math.min(value, maxValue))
end
function increase(start, increment, maxValue) -- com looping
result = start + increment; -- da estrada
if (result >= maxValue) then
result = result - maxValue;
end
if (result < 0) then
result = result + maxValue;
end
return result;
end
Estrada – Projeção e Draw
function love.draw()
drawRoad() -- desenha a estrada
end
16
function project(p, cameraX, cameraY, cameraZ, cameraDepth, width, height, roadWidth)
local proj = {} -- coordeanadas após projeção
-- translação das coordenadas de acordo com a câmera
p.camera.x = (p.world.x or 0) - cameraX
p.camera.y = (p.world.y or 0) - cameraY
p.camera.z = (p.world.z or 0) - cameraZ
-- escala de acordo com a posição z da ponto no mundo 3D
p.screen.scale = cameraDepth/p.camera.z
-- projeção das coodernadas (x,y) e da largura da metade do segmento
proj.x = p.screen.scale * p.camera.x
proj.y = p.screen.scale * p.camera.y
proj.w = p.screen.scale * roadWidth
-- escala do plano matemático normalizado para o computacional
p.screen.x = round((width/2) + (proj.x * width/2))
p.screen.y = round((height/2) - (proj.y * height/2))
p.screen.w = round((proj.w * width/2))
endFunção de arredondamento comum
Estrada - Draw
17
function drawRoad()
baseSegment = findSegment(position)
-- desenha a partir do baseSegment
for n = 0, (drawDistance)-1 do
segment = segments[((baseSegment["index"] + n) % tablelength(segments)) + 1]
segment["looped"] = segment["index"] < baseSegment["index"]
-- projeta p1 na tela 2D
project(segment["p1"], playerX, cameraHeight,
position -(segment.looped and trackLength or 0), cameraDepth, width, height, roadWidth)
-- projeta p2 na tela 2D
project(segment["p2"], playerX, cameraHeight,
position - (segment.looped and trackLength or 0),
cameraDepth, width, height, roadWidth)
-- desenha segmentos com p1 e p2 já projetados
drawSegment(width, lanes, segment["p1"]["screen"]["x"],
segment["p1"]["screen"]["y"], segment["p1"]["screen"]["w"],
segment["p2"]["screen"]["x"], segment["p2"]["screen"]["y"],
segment["p2"]["screen"]["w"], segment["color"])
end
end
Valor definido para controlar a quantidade de segmentos desenhados a cada quadro
Estrada - Draw
18
function drawSegment (width, lanes, x1, y1, w1, x2, y2, w2, color)
-- desenha piso do cenário (grama) no segmento atual
love.graphics.setColor(color.grass)
love.graphics.rectangle("fill", 0, y2, width, y1 - y2)
-- desenha segmento da rua sobre a grama já desenhada
drawPolygon(x1-w1, y1, x1+w1, y1, x2+w2, y2, x2-w2,y2, color.road)
if (color.lane) then
l1 = w1 / (8*lanes) -- largura inferior da faixa
l2 = w2 / (8*lanes) -- largura superior da faixa
lane_w1 = w1*2/lanes –- largura inferior da pista
lane_w2 = w2*2/lanes –- largura superior da pista
lane_x1 = x1 - w1 + lane_w1 –- posição x inferior da pista
lane_x2 = x2 - w2 + lane_w2 –- posição x superior da pista
for lane = 1, lanes-1 do -- loop de desenho das faixas
drawPolygon(lane_x1 - l1/2, y1, lane_x1 + l1/2, y1, lane_x2 + l2/2, y2, lane_x2 - l2/2, y2, color.lane)
lane_x1 = lane_x1 + lane_w1 -- incrementa posição para
lane_x2 = lane_x2 + lane_w2 -- desenhar próxima faixa
end
end
end
Cor das faixas (color.lane) só está definida se o segmento possuir esquema de cor LIGHT
A variável “lanes” corresponde ao número de pistas que a estrada possuirá. A função “drawPolygon” utiliza a função love.graphics.polygon para desenhar o polígono recebido, além de alterar a cor de desenho com a cor recebida em seu último parâmetro
Importante - Desabilitar VSync
19
function love.load()
-- desabilitar VSync para evitar problemas relacionados
-- à performance do jogo
love.window.setMode(width, height, {vsync=false})
(...)
end
Exercício 11) Adicione o sprite de carro ao exemplo disponibilizado e faça a animação
de acordo com a movimentação do jogador
Resources:http://www.inf.puc-rio.br/~psampaio/eng1000/resources/Pseudo3DEx1.zip
20
Curvas 3D
21
Rascunho de curvas em um mundo 3D
• Mudança real na estrutura física dos segmentos;
• Requer cálculos complexos de rotação 3D;
• Raramente utilizado em Pseudo3D.
Curvas Pseudo3D
22
Curvas falsas em um mundo Pseudo3D
• Não há mudanças na estrutura dos segmentos, apenas na projeção;
• Cálculos simples, sem nenhuma rotação envolvida;
• Método recomendado para curvas em Pseudo3D;
Curvas Pseudo3D
23
Curvas falsas em um mundo Pseudo3D
• Como funciona?
- É necessário apenas modificar a linha central (p1, p2) de cada segmento durante a projeção;
- Para o efeito de curva, basta inclinar levemente a linha central de segmentos consecutivos criando um formato de curva.
p1
p2
p1
p2
Curvas Pseudo3D
24
Curvas falsas em um mundo Pseudo3D
• Precisamos armazenar o valor que representa a inclinação de cada segmento na nossa estrutura de segmentos;
- Valores negativos indicam curvas à esquerda- Valores positivos indicam curvas à direita- Valores mais altos indicam curvas acentuadas- Valores mais baixos indicam curvas suaves
p1
p2
p1
p2
Estrada – Estrutura Revisada
25
function addSegment(curve) –- adiciona um segmento da estrada
n = tablelength(segments) + 1;
seg = {
index = n, -- index é o tamanho atual da tabela (+1 -> lua)
p1 = { world = { z = (n-1) * segmentLength }, camera = {},
screen = {} },
p2 = { world = { z = n * segmentLength }, camera = {}, screen = {} } ,
color = randomColorLightness(math.floor(n/rumbleLength)),
looped = false,
curve = curve –- valor da inclinação do segmento
}
table.insert(segments, seg)
end
Estrada – Estrutura Revisada
26
-- adiciona uma porção de segmentos, suavizando curvas quandonecessário
function addRoad(enter, hold, leave, curve)
for n = 0, enter-1 do
-- interpola valores para suavizar entrada da curva
addSegment(interpolate(0, curve, n/enter))
end
for n = 0, hold-1 do
addSegment(curve)
end
for n = 0, leave-1 do
-- interpola valores para suavizar saída da curva
addSegment(interpolate(curve, 0, n/leave))
end
end -- interpolação - adiciona ao valor inicial uma porcentagem do que resta para o valor finalfunction interpolate(a,b,percent) return a + (b-a)*percent
end
Estrada – Estrutura Revisada
27
-- Parâmetros de uma parcela da estrada (conjunto de segmentos)
ROAD = {
LENGTH = { NONE = 0, SHORT = 25, MEDIUM = 40, LONG = 80 },
CURVE = { NONE = 0, EASY = 2, MEDIUM = 4, HARD = 6 }
}
-- Adiciona parcelas de estrada retas
function addStraight(size)
size = size or ROAD.LENGTH.MEDIUM
addRoad(size, size, size, 0) –- sem curva nessa parcela
End
-- Adiciona parcelas de estrada com curvas
function addCurve(size, curve)
size = size or ROAD.LENGTH.MEDIUM
curve = curve or ROAD.CURVE.MEDIUM
addRoad(size, size, size, curve) –- parcela com curva
end
Estrada – Estrutura Revisada
28
-- Adiciona parcelas de estradas com curvas em S
function addSCurves()
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY)
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.MEDIUM)
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.EASY)
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY)
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.MEDIUM)
end
-- constrói a estrada completa adicionando as parcelas desejadas
function buildRoad()
(...)
addStraight(ROAD.LENGTH.SHORT/4) –- reta pequena
addSCurves() -- curva em S
addStraight(ROAD.LENGTH.LONG) -- reta comprida
addSCurves() -- curva em S
addCurve(ROAD.LENGTH.LONG, -ROAD.CURVE.MEDIUM) -- curva à esquerda...
addCurve(ROAD.LENGTH.LONG, ROAD.CURVE.MEDIUM) -- curva à direita...
addCurve(ROAD.LENGTH.LONG, -ROAD.CURVE.EASY) (...)
end
Estrada – Draw Revisado
29
function drawRoad()
baseSegment = findSegment(position)
basePercent = percentOf (position, segmentLength) –- evita transiçõesde segmentos bruscas
x = 0; -- deslocamento x em p1
dx = - (baseSegment["curve"] * basePercent) –- deslocamento x em p2 relativo ao x em p1
for n = 0, (drawDistance)-1 do
(...)
project(segment["p1"], playerX – x, (...)) -- desloca x da câmera
project(segment["p2"], playerX - x – dx, (...)) -- em p1 e em p2
-- atualiza valores de x e dx para próximo segmento
x = x + dx
dx = dx + segment["curve"]
(...)
end
end
-- retorna o percentual atingido por um valor em um totalfunction percentOf(n, total) return (n%total)/total
end
Exercício 21) Adicione ao exemplo disponibilizado uma força centrífuga que simule o
movimento de um veículo durante uma curva
Resources:http://www.inf.puc-rio.br/~psampaio/eng1000/resources/Pseudo3DEx2.zip
30
Adicionando Sprites
31
• Sprites 2D compatíveis com a visão da câmera
• Requer posicionamento e escala de acordo com a perspectiva do mundo
• Dois tipos de sprites: fixos e móveis
Adicionando Sprites
32
• Temos as informações necessárias para o desenho de sprites em cada ponto projetado!
• Podemos utilizar a escala, o x e y, e a largura da pista (w) ao projetarmos os sprites em um ponto de um segmento
• Passos para adicionar um sprite:• Armazenar em um segmento da pista• Atualizar o segmento atual do sprite (no caso de sprites móveis)• Desenhar o sprite de acordo com as informações contidas no segmento atual (utilizando p1’s ou p2’s)
function project(…)p.screen.scale = cameraDepth/p.camera.z
p.screen.x = (…)
p.screen.y = (…)
p.screen.w = (…)
end
Armazenando Sprites
33
function addSegment(…)
(…)
seg = {
(…)
sprites = {}, -- contém todos os sprites fixos de um segmento
cars = {}, -- contém todos os sprites móveis de um segmento
(…)
}
(…)
end
-- constantes relacionadas aos sprites
SPRITES = {
TREE1 = { x = 0, y = 50, w = 232, h = 153},
CAR01 = { x = 0, y = 0, w = 82, h = 46},
CAR02 = { x = 180, y = 0, w = 82, h = 46}
}
SPRITES.CARS = {SPRITES.CAR01, SPRITES.CAR02}
SPRITES.SCALE = 4.2
Armazenando Sprites
34
-- função que adiciona sprites fixos a um segmento n
function addSprite(n, sprite, offset)
-- offset é o deslocamento do sprite no eixo x
sprite = {source = sprite, offset = offset}
-- insere o sprite no segmento desejado
table.insert(segments[n]["sprites"], sprite)
end
-- função que popula a estrada com sprites fixos
function buildSprites()
addSprite(15, SPRITES.TREE1, -roadWidth) -- adiciona sprite TREE1 nos
addSprite(20, SPRITES.TREE1, -roadWidth) -- segmentos 15 e 20 à
-- esquerda da estrada
n = 21 -- a partir do segmento 21, adiciona sprite TREE1 à esquerda
-- e à direita da estrada, a cada rumbleLength * 2 segmentos
while n < tablelength(segments) do
addSprite(n, SPRITES.TREE1, -roadWidth)
addSprite(n, SPRITES.TREE1, roadWidth)
n = n + (rumbleLength * 2)
end
end
Armazenando Sprites
35
-- função que popula a estrada com sprites móveis
function buildCars()
cars = {} -- tabela auxiliar para facilitar a atualização dos sprites
-- insere sprites móveis na estrada randomicamente
for n = 0, totalCars do -- totalCars equivale a qtd de sprites móveis
-- randomiza um offset no eixo x para o sprite
-- OBS: randomChoice escolhe aleatóriamente um valor de uma tabelaoffset = math.random() * randomChoice({-0.8*roadWidth, 0.8*roadWidth})
-- randomiza posição z do sprite
z = math.floor(math.random() * tablelength(segments))*segmentLength
-- randomiza o sprite móvel a ser usado e sua velocidade
sprite = randomChoice(SPRITES.CARS)
carspeed = maxSpeed/4 + math.random() * maxSpeed
segment = findSegment(z)
car = { offset = offset, z = z, sprite = sprite, speed = carspeed, percent = 0, segment = segment}
-- insere o carro gerado na estrada e também na tabela auxiliar
table.insert(segment["cars"], car)
table.insert(cars, car)
end
end
Atualizando Sprites
36
-- função que atualiza posição dos sprites móveis (no ex. carros)
function updateCars(dt) -- deve ser chamado no love.update()
for n = 1, tablelength(cars) do -- percorre tabela de carros auxiliar
car = cars[n]
oldSegment = car["segment"] -- segmento pré-atualização do carro
-- atualiza posição z do carro de acordo com sua velocidade
car["z"] = increase(car["z"], dt * car["speed"], trackLength)
-- atualiza percentual percorrido do segmento atual
car["percent"] = percentOf(car["z"], segmentLength) -- facilita desenho
newSegment = findSegment(car["z"]) -- segmento pós-atualização
car["segment"] = newSegment -- atualiza segmento do carro
-- se novo segmento é diferente do antigo
-- troca o segmento do carro na estrutura da estrada
if (oldSegment ~= newSegment) then
table.remove(oldSegment["cars"], indexOf(oldSegment["cars"], car))
-- indexOf retorna indíce de um objeto em uma tabela
table.insert(newSegment["cars"], car)
end
end
end
Desenhando Sprites
37
-- função que desenha todos os sprites (deve ser chamada no love.draw())
function drawSprites()
-- loop em ordem contrária: algoritmo do pintor
-- desenhamos o que está atrás primeiro!
n = (drawDistance-1)
while n > 0 do
-- segmento atual do loop
segment = segments[((baseSegment["index"] + n) % tablelength(segments)) + 1]
(… desenha sprites fixos …)
(… desenha sprites móveis …)
(… desenha sprite do player …)
-- decrementa iterador do loop: algoritmo do pintor
n = n - 1
end
end
Desenhando Sprites
38
(… desenha sprites fixos …)
for i = 1 , tablelength(segment["sprites"]) do
sprite = segment["sprites"][i]
spriteScale = segment["p1"]["screen"]["scale"]
spriteX = segment["p1"]["screen"]["x"] +
(spriteScale * sprite["offset"] * width/2)
spriteY = segment["p1"]["screen"]["y"]
drawSprite(width, height, resolution, roadWidth, sprites, sprite["source"], spriteScale, spriteX, spriteY, (sprite["offset"] < 0 and -1 or 0), -1)
end
Desenhando Sprites
39
(… desenha sprites móveis …)
for i = 1 , tablelength(segment["cars"]) do
car = segment["cars"][i]
sprite = car["sprite"]
spriteScale = interpolate(segment["p1"]["screen"]["scale"], segment["p2"]["screen"]["scale"], car["percent"])
spriteX = interpolate(segment["p1"]["screen"]["x"], segment["p2"]["screen"]["x"], car["percent"]) + (spriteScale * car["offset"] * width/2)
spriteY = interpolate(segment["p1"]["screen"]["y"], segment["p2"]["screen"]["y"], car["percent"])
drawSprite(width, height, resolution, roadWidth, sprites, car["sprite"], spriteScale, spriteX, spriteY, -0.5, -1)
end
Desenhando Sprites
40
(… desenha sprite do player …)
if (segment == playerSegment) then
drawSprite(width, height, resolution, roadWidth, sprites, SPRITES.CAR02,
cameraDepth/playerZ,
width/2,
(height/2),
-0.5,
1);
end
-- atualizado dentro do love.update()playerSegment = findSegment(position+playerZ) -- position + playerZ eqvuiale à:-- posição da câmera + distância(fixa) do player para câmera
Desenhando Sprites
41
-- função que desenha sprites aplicando escalas e offsets
-- de acordo com parâmetros passados
function drawSprite(width, height, resolution, roadWidth, sprites, sprite, scale, destX, destY, offsetX, offsetY)
destW = (sprite.w * scale * width) * SPRITES.SCALE
destH = (sprite.h * scale * width) * SPRITES.SCALE
destX = destX + (destW * offsetX)
destY = destY + (destH * offsetY)
quad = love.graphics.newQuad(sprite.x, sprite.y, sprite.w, sprite.h, sprites:getDimensions())
love.graphics.draw(sprites, quad, destX, destY, 0, (destW)/sprite.w, (destH )/sprite.h)
end
Collision Check
42
n segmentos
• Basta checar um número de segmentos no entorno do segmento atual do player
• Se em algum dos segmentos checados houver sprites, precisamos apenas verificar se houve colisão no eixo x
• Para colisão com sprites fixos, checar o segmento atual do player já é o suficiente
Exercícios1) Implemente a detecção de colisões entre o player e os sprites (fixos e
móveis) da estrada. Utilize o exemplo disponibilizado como base.OBS: o exemplo disponibilizado possui uma função pronta para checarcolisão no eixo x (function overlap) que recebe o x e o width de dois sprites e retorna “true” caso haja colisão. Lembre-se que o width é multiplicado pelo SPRITES.SCALE
Resources:http://www.inf.puc-rio.br/~psampaio/eng1000/resources/Pseudo3DEx3.zip
43
Leitura Complementar
• Lou's Pseudo 3d Page– http://www.inf.puc-rio.br/~psampaio/eng1000/resources/LouPseudo3D.pdf
44
Recommended