Skip to content

Commit

Permalink
Separate lighting into its own render step
Browse files Browse the repository at this point in the history
  • Loading branch information
eira-fransham committed Jun 12, 2020
1 parent 71178c9 commit 25507a1
Show file tree
Hide file tree
Showing 14 changed files with 433 additions and 439 deletions.
21 changes: 7 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
# Göld - Löve but for Goldsrc

This is a simple, LuaJIT-based game engine that allows people to write full 3D games in Lua. It
intends to emulate the simplicity and fun of modding games on Goldsrc/Source and the first 3 Quake
engines, but with something closer to Löve's attitude where native code is used for performance-
intensive things but Lua has as much control as possible. Having said that, I'm perfectly happy to
restrict things significantly if it makes things simpler, and as long as it'd be somewhat reasonable
to implement a game of similar technical complexity to Half-Life 1 on this then I'd be happy.

Currently all this does is load and render Quake 2 maps, although it does render them pretty nicely.
I hope to eventually seamlessly be able to load Goldsrc and Quake 1 maps, too, and possibly even
Quake 3 maps if it's not too much extra work (it's a format which lends itself far better to being
rendered on the GPU). Goldsrc and Quake 1 store mipmapped textures inline, though, so it's not just
a case of just converting it to the same structure as a Quake 2 map while loading. That's really the
only hurdle to implementing Goldsrc and Quake 1 maps, however.
This is designed to be a game engine for hacking together 3D games using old tech. It's based on the
simple mental model of PyGame or Löve, but for Goldsrc/Quake-era tech. Right now it can load Quake 2
maps and display them, including the skybox textures, but Goldsrc
models and display

![Screenshot 1](screenshots/01.png)

![Screenshot 2](screenshots/02.png)

![Screenshot 3](screenshots/03.png)

![Screenshot 4](screenshots/04.png)

I've also got a parser for the monstrosity that is the Goldsrc .mdl format, so my next task is to
get Half-Life 1 models loading, displaying and animating in this engine. It's very, very likely that
I'll switch to updating the already-existing bindings to `assimp` and using those instead, since I'm
Expand Down Expand Up @@ -55,4 +48,4 @@ for vals in os.walk("."):
If you don't do this, you'll see something which I'm sure will be familiar to many modders and
tinkerers of this era of games:

![Missing texture](screenshots/04.png)
![Missing texture](screenshots/missing.png)
91 changes: 88 additions & 3 deletions bsp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use arrayvec::ArrayString;
use bitflags::bitflags;
use bv::BitVec;
pub use goldsrc_format_common::{
parseable, CoordSystem, ElementSize, Magic, QVec, SimpleParse, XEastYDownZSouth,
XEastYNorthZUp, XEastYSouthZUp, V3,
parseable, CoordSystem, ElementSize, QVec, SimpleParse, XEastYDownZSouth, XEastYNorthZUp,
XEastYSouthZUp, V3,
};
use std::{
convert::{TryFrom, TryInto},
Expand All @@ -32,6 +32,52 @@ fn error(msg: impl ToString) -> io::Error {
panic!("{}", msg.to_string())
}

macro_rules! magic {
(struct $name:ident($magic:expr);) => {
#[derive(PartialEq, Default, Copy, Clone)]
pub struct $name;

impl fmt::Debug for $name {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
$magic.fmt(f)
}
}

impl $name {
pub const fn into_inner(self) -> [u8; 4] {
$magic
}
}

impl Deref for $name {
type Target = [u8; 4];

fn deref(&self) -> &Self::Target {
&$magic
}
}

impl ElementSize for $name {
const SIZE: usize = <[u8; 4]>::SIZE;
}

impl SimpleParse for $name {
fn parse<R: io::Read>(r: &mut R) -> io::Result<Self> {
let val = <[u8; 4]>::parse(r)?;

if val == $magic {
Ok($name)
} else {
Err(error(format!(
"Invalid magic number: expected {:?}, got {:?}",
$magic, val
)))
}
}
}
};
}

#[derive(Debug, Default, PartialEq)]
pub struct GenericDirectories {
area_portals: DirEntry,
Expand Down Expand Up @@ -119,8 +165,12 @@ impl ElementSize for NotApplicable {
const SIZE: usize = usize::MAX;
}

magic! {
struct Q2Magic(QUAKE2_MAGIC);
}

impl BspFormat for Quake2 {
type Magic = Magic<QUAKE2_MAGIC>;
type Magic = Q2Magic;

const VERSION: u32 = 0x26;

Expand Down Expand Up @@ -1755,6 +1805,35 @@ impl Q2Model {
}

impl<'a> Handle<'a, Bsp, Q2Model> {
// #[inline]
// pub fn leaf_indices(self) -> Option<impl Iterator<Item = u32> + Clone + 'a> {
// use itertools::Either;

// let mut stack = vec![Either::Left((self.headnode, self.bsp.node(self.headnode as usize)?))];

// Some(iter::from_fn(move || loop {
// let next = stack.pop()?;
// let node = match next {
// Either::Left(node) => node,
// Either::Right(leaf) => break Some(leaf),
// };
// let [left, right] = node.children;
// let left = if left < 0 {
// Either::Right(self.bsp.leaf(-(left + 1) as usize)?)
// } else {
// Either::Left(self.bsp.node(left as usize)?)
// };
// let right = if right < 0 {
// Either::Right(self.bsp.leaf(-(right + 1) as usize)?)
// } else {
// Either::Left(self.bsp.node(right as usize)?)
// };

// stack.push(left);
// stack.push(right);
// }))
// }

#[inline]
pub fn leaves(self) -> Option<impl Iterator<Item = Handle<'a, Bsp, Q2Leaf>> + Clone + 'a> {
use itertools::Either;
Expand Down Expand Up @@ -1955,6 +2034,12 @@ impl<'a> Handle<'a, Bsp, Face> {
self.vert_indices()
.map(move |i| &self.bsp.vertices[i as usize])
}

#[inline]
pub fn center(self) -> QVec {
let tot: QVec = self.vertices().cloned().sum();
tot / (self.vert_indices().len() as f32)
}
}

impl<'a, T> Handle<'a, T, Node>
Expand Down
71 changes: 18 additions & 53 deletions goldsrc-format-common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#![cfg_attr(feature = "nightly", feature(const_generics))]

use arrayvec::{Array, ArrayString, ArrayVec};
use byteorder::{LittleEndian, ReadBytesExt};
use std::{io, iter, num};
Expand All @@ -14,57 +12,6 @@ fn error(msg: impl ToString) -> io::Error {
panic!("{}", msg.to_string())
}

#[cfg(feature = "nightly")]
mod magic {
use super::{ElementSize, SimpleParse};
use std::{fmt, io, ops};

#[derive(PartialEq, Default, Copy, Clone)]
pub struct Magic<const VALUE: [u8; 4]>;

impl<const V: [u8; 4]> fmt::Debug for Magic<V> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
V.fmt(f)
}
}

impl<const V: [u8; 4]> Magic<V> {
pub const fn into_inner(self) -> [u8; 4] {
V
}
}

impl<const V: [u8; 4]> ops::Deref for Magic<V> {
type Target = [u8; 4];

fn deref(&self) -> &Self::Target {
&V
}
}

impl<const V: [u8; 4]> ElementSize for Magic<V> {
const SIZE: usize = <[u8; 4]>::SIZE;
}

impl<const V: [u8; 4]> SimpleParse for Magic<V> {
fn parse<R: io::Read>(r: &mut R) -> io::Result<Self> {
let val = <[u8; 4]>::parse(r)?;

if val == V {
Ok(Magic)
} else {
Err(super::error(format!(
"Invalid magic number: expected {:?}, got {:?}",
V, val
)))
}
}
}
}

#[cfg(feature = "nightly")]
pub use magic::Magic;

pub trait ElementSize {
const SIZE: usize;
}
Expand Down Expand Up @@ -371,6 +318,24 @@ impl<C> std::ops::Mul<f32> for V3<C> {
}
}

impl<C> std::iter::Sum<Self> for V3<C>
where
C: Default,
{
fn sum<I>(mut iter: I) -> Self
where
I: Iterator<Item = Self>,
{
let mut out = iter.next().unwrap_or(V3([0., 0., 0.], C::default()));

for v in iter {
out = out + v;
}

out
}
}

impl<C> V3<C> {
pub fn x(&self) -> f32 {
self.0[0]
Expand Down
Binary file modified screenshots/04.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/missing.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 3 additions & 43 deletions shaders/models.frag
Original file line number Diff line number Diff line change
@@ -1,38 +1,14 @@
#version 450
#pragma shader_stage(fragment)

#define MAX_LIGHTS 16
#define LIGHT_MULTIPLIER 1
#define LIGHT_FALLOFF 1
#define LIGHT_FALLOFF_SCALE 10

layout(location = 0) in vec2 v_TexCoord;
layout(location = 1) in flat uvec4 v_Tex;
layout(location = 2) in vec3 v_Pos;
layout(location = 3) in vec3 v_Normal;

layout(location = 0) out vec4 outColor;

layout(set = 0, binding = 1) uniform texture2D t_Diffuse;
layout(set = 0, binding = 2) uniform sampler s_Color;

struct Light {
vec4 pos;
vec4 color;
};

layout(set = 0, binding = 3) uniform Locals {
vec4 _unused;
float ambientLight;
};

layout(set = 0, binding = 5) uniform Lights {
Light lights[MAX_LIGHTS];
};
layout(set = 0, binding = 6) uniform NumLights {
uint numLights;
};

float ifLt(float a, float b, float ifTrue, float ifFalse) {
float lt = step(b, a);

Expand All @@ -55,25 +31,9 @@ void main() {
)
);

vec3 shadedAmount = vec3(ambientLight);

for (uint i = 0; i < MAX_LIGHTS; i++) {
vec3 lightVec = lights[i].pos.xyz - v_Pos;

float dist = length(lightVec) * LIGHT_FALLOFF_SCALE;
float amt = LIGHT_MULTIPLIER * lights[i].color.a / (1.0 + pow(dist, LIGHT_FALLOFF));
float normAmt = max(dot(v_Normal, normalize(lightVec)), 0);

amt = ifLt(amt, 0.001, 0, amt);

shadedAmount += step(i + 1, numLights) *
amt *
pow(normAmt, 2) *
lights[i].color.rgb;
}

outColor = vec4(shadedAmount, 1) * texture(
sampler2D(t_Diffuse, s_Color), (offset + v_Tex.xy) /
outColor = texture(
sampler2D(t_Diffuse, s_Color),
(offset + v_Tex.xy) /
textureSize(sampler2D(t_Diffuse, s_Color), 0)
);
}
10 changes: 1 addition & 9 deletions shaders/models.vert
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,12 @@

#include <common.vert.inc>

layout(location = 3) in vec3 a_Normal;

layout(location = 2) out vec3 v_Pos;
layout(location = 3) out vec3 v_Normal;

layout(set = 0, binding = 4) uniform ModelData {
layout(set = 0, binding = 3) uniform ModelData {
mat4 translation;
};

void main() {
vec4 pos = translation * a_Pos;

v_Pos = pos.xyz;
v_Normal = a_Normal;

transformTexturedVertex(u_View, u_Proj, pos);
}
12 changes: 9 additions & 3 deletions shaders/post.frag
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ layout(location = 0) in vec2 v_TexCoord;
layout(location = 0) out vec4 outColor;

layout(set = 0, binding = 0) uniform texture2D t_Diffuse;
layout(set = 0, binding = 1) uniform sampler s_Color;
layout(set = 0, binding = 2) uniform Locals {
layout(set = 0, binding = 1) uniform texture2D t_Lights;
layout(set = 0, binding = 2) uniform sampler s_Color;
layout(set = 0, binding = 3) uniform Locals {
float invGamma;
float intensity;
};
Expand Down Expand Up @@ -81,5 +82,10 @@ vec4 reinhard(vec4 color) {
}

void main() {
outColor = aces(texture(sampler2D(t_Diffuse, s_Color), v_TexCoord));
vec4 diffuse = texture(sampler2D(t_Diffuse, s_Color), v_TexCoord);
vec4 lights = texture(sampler2D(t_Lights, s_Color), v_TexCoord);

vec3 combined = diffuse.rgb * (1 - lights.a) + diffuse.rgb * lights.rgb * lights.a;

outColor = vec4(aces(combined), 1);
}
Loading

0 comments on commit 25507a1

Please sign in to comment.