engine/render/vulkan: analytic lighting and PBR basics

We implement a simple PBR shader that uses off-the-shelf maths from
[Kar13]. I attempt to trace and cite as much as possible, but the amount
of different approaches from both industry and academia makes it quite
difficult to find exact papers to cite - especially as my mathematical
skills are not high enough to derive, or check the derivation for any of
these myself.

[Kar13]
Brian Karis. 2013. "Real Shading in Unreal Engine 4"
URL: https://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
ecs
q3k 2020-07-23 17:45:24 +02:00
parent 1a52782882
commit 29ff5f6eb4
10 changed files with 425 additions and 69 deletions

View File

@ -13,6 +13,7 @@ rust_binary(
"src/main.rs",
"src/physics/mod.rs",
"src/physics/color.rs",
"src/render/light.rs",
"src/render/material.rs",
"src/render/mesh.rs",
"src/render/mod.rs",

View File

@ -2,11 +2,197 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
struct OmniLight {
vec4 pos;
vec4 color;
};
layout(push_constant) uniform PushConstantObject {
mat4 view;
vec4 cameraPos;
OmniLight omniLights[3];
} pco;
layout(binding = 0) uniform sampler2D texSamplerDiffuse;
layout(binding = 1) uniform sampler2D texSamplerRoughness;
layout(location = 0) in vec2 fragTexCoord;
layout(location = 1) in vec3 fragWorldPos;
layout(location = 2) in vec3 fragNormal;
layout(location = 0) out vec4 outColor;
void main() {
outColor = texture(texSamplerDiffuse, fragTexCoord) * texture(texSamplerRoughness, fragTexCoord).x;
const float PI = 3.14159;
// We implement a Lambertiand & Cook-Torrance BRDF-based lighting system.
// All of this is based on a number of scientific papers, meta-studies and modern sources. We do
// our best to cite as much as possible for future reference.
// Most of the maths is used straight from [Kar13].
//
// A good summary of different research is available this blog post by Brian Karis, that attempts
// to catalogue all existing BRDF-related functions:
// http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
/// Bibliography:
//
// [Bec63]
// P. Beckmann & A. Spizzichino. 1963. "The Scattering of Electromagnetic Waves from Rough Surfaces"
// MacMillan, New York
//
// [Smi67]
// Bruce Smith. 1967. "Geometrical shadowing of a random rough surface."
// IEEE transactions on antennas and propagation 15.5 (1967): 668-671.
//
// [CT82]
// Robert L. Cook, Kenneth E. Torrance. 1982. "A Reflectance Model for Computer Graphics"
// ACM Transactions on Graphics, 1(1), 724.
// doi: 10.1145/357290.357293
//
// [Sch94]
// Christophe Schlick. 1994. "An Inexpensive BRDF Model for Physically-based Rendering"
// Computer Graphics Forum, 13(3), 233246.
// doi: 10.1111/1467-8659.1330233
//
// [Wa07]
// Bruce Walter et al. 2007. "Microfacet Models for Refraction through Rough Surfaces."
// Proceedings of the Eurographics Symposium on Rendering.
//
// [Bur12]
// Brent Burley. 2012. "Physically-Based Shading at Disney"
// URL: https://disney-animation.s3.amazonaws.com/library/s2012_pbs_disney_brdf_notes_v2.pdf
//
// [Kar13]
// Brian Karis. 2013. "Real Shading in Unreal Engine 4"
// URL: https://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
//
// [Hei14]
// Eric Heitz. 2014. "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs"
// Journal of Computer Graphics Techniques, 3 (2).
//
// [GA19]
// Romain Guy, Mathias Agopian, "Physically Based Rendering in Filament"
// URL: https://google.github.io/filament/Filament.html
// [Sch94] Fresnel approximation, used for F in Cook-Torrance BRDF.
vec3 FresnelSchlick(float HdotV, vec3 F0) {
return F0 + (1.0 - F0) * pow(1.0 - HdotV, 5.0);
}
// Microfacet Normal Distribution Function, used for D in Cook-Torrance BRDF.
float DistributionGGX(float NdotH, float roughness) {
// 'Roughness remapping' as per [Bur12]
float a = roughness * roughness;
// NDF from [Kar13], that cites [Bur12], which in turn cites [Wa07].
// However, I could not find the same equation form in [Bur12] or deduce it myself from [Wa07],
// and ended up taking the direct, untraceable form from [Kar13], so take this with a grain of salt.
float a2 = a * a;
float NdotH2 = NdotH * NdotH;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
return (a * a) / (PI * denom * denom);
}
float GeometrySchlickGGX(float NdotV, float roughness) {
// Remapping of K for analytical (non-IBL) lighting per [Kar13].
float r = (roughness + 1.0);
float k = (r * r) / 8.0;
// [Sch94] approximation of [Smi67] equation for [Bec63].
return (NdotV) / (NdotV * (1.0 - k) + k);
}
// Geometric shadowing function, used for G in Cook-Torrance BRDF.
float GeometrySmith(float NdotV, float NdotL, float roughness) {
// Smith geometric shadowing function.
// [GA19] cites [Hei14] as demonstrating [Smi97] to be correct.
float ggx2 = GeometrySchlickGGX(NdotV, roughness);
float ggx1 = GeometrySchlickGGX(NdotL, roughness);
return ggx1 * ggx2;
}
// Cook-Torrance [CT82] specular model.
vec3 SpecularCookTorrance(float NdotH, float NdotV, float NdotL, vec3 F, float roughness) {
float NDF = DistributionGGX(NdotH, roughness);
float G = GeometrySmith(NdotV, NdotL, roughness);
// F is taken in as a pre-computed argument for optimization purposes (it's reused for the
// lambertian component of the lighting model).
// Form from [Kar13], decuced from [CT82].
vec3 specular = (NDF * G * F) / max((4.0 * NdotV * NdotL), 0.0001);
return specular;
}
void main() {
vec3 cameraPos = pco.cameraPos.xyz / pco.cameraPos.w;
// Normal of this fragment.
vec3 N = normalize(fragNormal);
// Unit vector pointing at camera from this fragment.
vec3 V = normalize(cameraPos - fragWorldPos);
// Texture parameters for this fragment.
vec3 albedo = texture(texSamplerDiffuse, fragTexCoord).xyz;
float roughness = texture(texSamplerRoughness, fragTexCoord).x;
float metallic = 0.0;
float dielectric = 1.0 - metallic;
// Absolute Specular Reflectance at normal incidence. Ie., the base reflectivity of a
// material when looking straight at it.
// Trick from https://learnopengl.com/PBR/Theory : encode the reflectivity in the albedo for
// metallic materials (as they have no albedo). Otherwise, default to a typical reflectivity
// for non-metallic (dielectric) materials (0.04).
vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo, metallic);
// Luminance of this fragment.
// Luminance is defined as the sum (integral) of all ilncoming illuminance over the half-sphere
// 'above' that point. As we currently only support analytic lighting (ie. omni lights), we
// integrate by iterating over all luminance sources, that currently are point lights.
vec3 Lo = vec3(0.0);
for (int i = 0; i < 3; ++i) {
vec3 lightPos = pco.omniLights[i].pos.xyz;
vec3 lightColor = pco.omniLights[i].color.xyz;
// Unit vector pointing at light from fragment.
vec3 L = normalize(lightPos - fragWorldPos);
// Half-vector between to-light and to-camera unit vectors.
vec3 H = normalize(V + L);
// Dot products re-used across further computation for this (fragment, light) pair.
float HdotV = max(dot(H, V), 0.0);
float NdotH = max(dot(N, H), 0.0);
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
// Translate luminous flux (lumen) into luminous intensity at this solid angle (candela).
// This follows the derivation in [GA19] (58).
float distance = length(lightPos - fragWorldPos);
vec3 intensity = (lightColor / (4 * PI * (distance * distance)));
// The Fresnel component from the Cook-Torrance specular BRDF is also used to calculate the
// lambertian diffuse weight kD. We calculate it outside of the function.
vec3 F = FresnelSchlick(HdotV, F0);
// Cook-Torrance specular value.
vec3 specular = SpecularCookTorrance(NdotH, NdotV, NdotL, F, roughness);
// Lambertian diffuse component, influenced by fresnel and dielectric/metalness.
vec3 kD = (vec3(1.0) - F) * dielectric;
// Lambertian diffuse value.
vec3 diffuse = albedo / PI;
// Illuminance for this point from this light is a result of scaling the luminous
// intensity of this light by the BRDL and by (N o L). This follows the definitions
// of illuminance and luminous intensity.
vec3 Li = (kD * diffuse + specular) * intensity * NdotL;
// Integration of luminance from illuminance.
Lo += Li;
}
vec3 ambient = vec3(0.03) * albedo;
vec3 color = ambient + Lo;
outColor = vec4(color, 1.0);
}

View File

@ -1,25 +1,38 @@
// vim: set ft=glsl:
#version 450
struct OmniLight {
vec3 pos;
vec3 color;
};
layout(push_constant) uniform UniformBufferObject {
mat4 view;
vec3 cameraPos;
OmniLight omniLights[2];
} ubo;
layout(location = 0) in vec3 pos;
layout(location = 1) in mat4 model;
layout(location = 5) in vec2 tex;
layout(location = 1) in vec3 normal;
layout(location = 2) in mat4 model;
layout(location = 6) in vec2 tex;
layout(location = 0) out vec2 fragTexCoord;
layout(location = 1) out vec3 fragWorldPos;
layout(location = 2) out vec3 fragNormal;
out gl_PerVertex {
vec4 gl_Position;
};
void main() {
gl_Position = ubo.view * model * vec4(pos, 1.0);
vec4 world = model * vec4(pos, 1.0);
fragTexCoord = tex;
fragNormal = normal;
fragWorldPos = world.xyz / world.w;
gl_Position = ubo.view * world;
// Vulkanize
gl_Position.y = -gl_Position.y;
}

View File

@ -10,9 +10,10 @@ mod util;
mod physics;
use render::vulkan::data;
use render::light::Omni;
use render::material::{Texture, PBRMaterialBuilder};
use render::mesh::Mesh;
use render::renderable::{Object, Renderable, ResourceManager};
use render::renderable::{Light, Object, Renderable, ResourceManager};
use physics::color;
fn main() {
@ -23,35 +24,35 @@ fn main() {
let mesh = {
let vertices = Arc::new(vec![
data::Vertex::new([-0.5, -0.5, 0.5], [1.0, 1.0, 1.0], [1.0, 0.0]),
data::Vertex::new([0.5, -0.5, 0.5], [1.0, 1.0, 0.0], [0.0, 0.0]),
data::Vertex::new([0.5, 0.5, 0.5], [0.0, 1.0, 1.0], [0.0, 1.0]),
data::Vertex::new([-0.5, 0.5, 0.5], [1.0, 0.0, 1.0], [1.0, 1.0]),
data::Vertex::new([-0.5, -0.5, 0.5], [ 0.0, 0.0, 1.0], [1.0, 0.0]),
data::Vertex::new([ 0.5, -0.5, 0.5], [ 0.0, 0.0, 1.0], [0.0, 0.0]),
data::Vertex::new([ 0.5, 0.5, 0.5], [ 0.0, 0.0, 1.0], [0.0, 1.0]),
data::Vertex::new([-0.5, 0.5, 0.5], [ 0.0, 0.0, 1.0], [1.0, 1.0]),
data::Vertex::new([0.5, -0.5, -0.5], [1.0, 1.0, 1.0], [0.0, 1.0]),
data::Vertex::new([0.5, 0.5, -0.5], [1.0, 1.0, 0.0], [1.0, 1.0]),
data::Vertex::new([0.5, 0.5, 0.5], [0.0, 1.0, 1.0], [1.0, 0.0]),
data::Vertex::new([0.5, -0.5, 0.5], [1.0, 0.0, 1.0], [0.0, 0.0]),
data::Vertex::new([ 0.5, -0.5, -0.5], [ 1.0, 0.0, 0.0], [0.0, 1.0]),
data::Vertex::new([ 0.5, 0.5, -0.5], [ 1.0, 0.0, 0.0], [1.0, 1.0]),
data::Vertex::new([ 0.5, 0.5, 0.5], [ 1.0, 0.0, 0.0], [1.0, 0.0]),
data::Vertex::new([ 0.5, -0.5, 0.5], [ 1.0, 0.0, 0.0], [0.0, 0.0]),
data::Vertex::new([-0.5, -0.5, -0.5], [1.0, 1.0, 1.0], [1.0, 1.0]),
data::Vertex::new([-0.5, 0.5, -0.5], [1.0, 1.0, 0.0], [0.0, 1.0]),
data::Vertex::new([-0.5, 0.5, 0.5], [0.0, 1.0, 1.0], [0.0, 0.0]),
data::Vertex::new([-0.5, -0.5, 0.5], [1.0, 0.0, 1.0], [1.0, 0.0]),
data::Vertex::new([-0.5, -0.5, -0.5], [-1.0, 0.0, 0.0], [1.0, 1.0]),
data::Vertex::new([-0.5, 0.5, -0.5], [-1.0, 0.0, 0.0], [0.0, 1.0]),
data::Vertex::new([-0.5, 0.5, 0.5], [-1.0, 0.0, 0.0], [0.0, 0.0]),
data::Vertex::new([-0.5, -0.5, 0.5], [-1.0, 0.0, 0.0], [1.0, 0.0]),
data::Vertex::new([-0.5, -0.5, -0.5], [1.0, 1.0, 1.0], [0.0, 1.0]),
data::Vertex::new([0.5, -0.5, -0.5], [1.0, 1.0, 0.0], [1.0, 1.0]),
data::Vertex::new([0.5, -0.5, 0.5], [0.0, 1.0, 1.0], [1.0, 0.0]),
data::Vertex::new([-0.5, -0.5, 0.5], [1.0, 0.0, 1.0], [0.0, 0.0]),
data::Vertex::new([-0.5, -0.5, -0.5], [ 0.0, -1.0, 0.0], [0.0, 1.0]),
data::Vertex::new([ 0.5, -0.5, -0.5], [ 0.0, -1.0, 0.0], [1.0, 1.0]),
data::Vertex::new([ 0.5, -0.5, 0.5], [ 0.0, -1.0, 0.0], [1.0, 0.0]),
data::Vertex::new([-0.5, -0.5, 0.5], [ 0.0, -1.0, 0.0], [0.0, 0.0]),
data::Vertex::new([-0.5, 0.5, -0.5], [1.0, 1.0, 1.0], [1.0, 1.0]),
data::Vertex::new([0.5, 0.5, -0.5], [1.0, 1.0, 0.0], [0.0, 1.0]),
data::Vertex::new([0.5, 0.5, 0.5], [0.0, 1.0, 1.0], [0.0, 0.0]),
data::Vertex::new([-0.5, 0.5, 0.5], [1.0, 0.0, 1.0], [1.0, 0.0]),
data::Vertex::new([-0.5, 0.5, -0.5], [ 0.0, 1.0, 0.0], [1.0, 1.0]),
data::Vertex::new([ 0.5, 0.5, -0.5], [ 0.0, 1.0, 0.0], [0.0, 1.0]),
data::Vertex::new([ 0.5, 0.5, 0.5], [ 0.0, 1.0, 0.0], [0.0, 0.0]),
data::Vertex::new([-0.5, 0.5, 0.5], [ 0.0, 1.0, 0.0], [1.0, 0.0]),
data::Vertex::new([-0.5, -0.5, -0.5], [1.0, 1.0, 1.0], [0.0, 0.0]),
data::Vertex::new([0.5, -0.5, -0.5], [1.0, 1.0, 0.0], [1.0, 0.0]),
data::Vertex::new([0.5, 0.5, -0.5], [0.0, 1.0, 1.0], [1.0, 1.0]),
data::Vertex::new([-0.5, 0.5, -0.5], [1.0, 0.0, 1.0], [0.0, 1.0]),
data::Vertex::new([-0.5, -0.5, -0.5], [ 0.0, 0.0, -1.0], [0.0, 0.0]),
data::Vertex::new([ 0.5, -0.5, -0.5], [ 0.0, 0.0, -1.0], [1.0, 0.0]),
data::Vertex::new([ 0.5, 0.5, -0.5], [ 0.0, 0.0, -1.0], [1.0, 1.0]),
data::Vertex::new([-0.5, 0.5, -0.5], [ 0.0, 0.0, -1.0], [0.0, 1.0]),
]);
let indices = Arc::new(vec![
0, 1, 2, 2, 3, 0,
@ -87,7 +88,13 @@ fn main() {
}
}
let renderables: Vec<Box<dyn Renderable>> = cubes.into_iter().map(|e| e as Box<dyn Renderable>).collect();
let light1 = rm.add_light(Omni::test(cgm::Vector3::new(-10.0, -10.0, -5.0)));
let light2 = rm.add_light(Omni::test(cgm::Vector3::new(10.0, 10.0, -5.0)));
let mut renderables: Vec<Box<dyn Renderable>> = cubes.into_iter().map(|e| e as Box<dyn Renderable>).collect();
renderables.push(Box::new(Light{ light: light1 }));
renderables.push(Box::new(Light{ light: light2 }));
let start = time::Instant::now();
let mut renderer = render::Renderer::initialize();
@ -97,17 +104,29 @@ fn main() {
let position = (instant / 10.0) * 3.14 * 2.0;
let camera = cgm::Point3::new(
//position.cos() * 10.0 * (((position*2.0).cos()/2.0)+1.0),
//position.sin() * 10.0 * (((position*2.0).cos()/2.0)+1.0),
7.0 + (position / 4.0).sin(),
12.0 + (position / 4.0).cos(),
3.0
);
if let Some(light) = rm.light_mut(&light1) {
light.position = cgm::Vector3::new(
-0.0 + (position*3.0).sin() * 4.0,
-0.0 + (position*4.0).cos() * 4.0,
-0.0 + (position*2.0).sin() * 1.0,
);
}
let view = cgm::Matrix4::look_at(
cgm::Point3::new(
position.cos() * 10.0 * (((position*2.0).cos()/2.0)+1.0),
position.sin() * 10.0 * (((position*2.0).cos()/2.0)+1.0),
3.0
),
camera.clone(),
cgm::Point3::new(0.0, 0.0, 0.0),
cgm::Vector3::new(0.0, 0.0, 1.0)
);
renderer.draw_frame(&view, &rm, &renderables);
renderer.draw_frame(&camera, &view, &rm, &renderables);
if renderer.poll_close() {
return;
}

View File

@ -0,0 +1,43 @@
use std::time;
use cgmath as cgm;
use crate::physics::color;
use crate::render::vulkan::data;
/// An Omni point light, with position in 3d space, and 'color' defined in lumens per CIE XYZ
/// color channel.
pub struct Omni {
pub position: cgm::Vector3<f32>,
/// Luminour power/flux defined as lumens per XYZ color channel.
pub color: color::XYZ,
pub id: u64,
}
impl Omni {
/// Make a test light. This has... a color. It's kinda yellow. And something close to 650
/// lumens of luminous power.
// TODO(q3k): implement [Kry85] (eq. 68) somewhere in //engine/src/physics for generation
// of nice lights colours from color temperature.
//
// [Kry85]
// M. Krystek. 1985. "An algorithm to calculate correlated color temperature"
// Color Research & Application, 10 (1), 3840.
pub fn test(position: cgm::Vector3<f32>) -> Self {
Self {
position,
color: color::XYZ::new(234.7, 214.1, 207.9),
// TODO: use a better method
id: time::SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap().as_nanos() as u64,
}
}
pub fn vulkan_uniform(&self) -> data::OmniLight {
// TODO: cache this?
data::OmniLight {
pos: [self.position.x, self.position.y, self.position.z, 1.0],
color: [self.color.x, self.color.y, self.color.z, 0.0],
}
}
}

View File

@ -15,10 +15,11 @@ use vulkano_win::VkSurfaceBuild;
use vulkano::instance as vi;
use vulkano::swapchain as vs;
pub mod vulkan;
pub mod light;
pub mod material;
pub mod mesh;
pub mod renderable;
pub mod vulkan;
const WIDTH: u32 = 800;
const HEIGHT: u32 = 600;
@ -52,11 +53,12 @@ impl Renderer {
pub fn draw_frame(
&mut self,
camera: &cgm::Point3<f32>,
view: &cgm::Matrix4<f32>,
rm: &renderable::ResourceManager,
renderables: &Vec<Box<dyn renderable::Renderable>>
) {
self.instance.flip(view, rm, renderables);
self.instance.flip(camera, view, rm, renderables);
}
pub fn poll_close(&mut self) -> bool {

View File

@ -3,18 +3,31 @@ use std::hash;
use cgmath as cgm;
use crate::render::light::Omni;
use crate::render::material::Material;
use crate::render::mesh::Mesh;
pub struct ResourceManager {
meshes: HashMap<u64, Mesh>,
materials: HashMap<u64, Material>,
lights: HashMap<u64, Omni>,
}
#[derive(Copy, Clone)]
pub enum ResourceID {
Material(u64),
Mesh(u64),
Light(u64),
}
impl ResourceID {
pub fn id(&self) -> u64 {
match self {
ResourceID::Material(i) => *i,
ResourceID::Mesh(i) => *i,
ResourceID::Light(i) => *i,
}
}
}
impl hash::Hash for ResourceID {
@ -22,21 +35,14 @@ impl hash::Hash for ResourceID {
match self {
ResourceID::Material(i) => i.hash(state),
ResourceID::Mesh(i) => i.hash(state),
ResourceID::Light(i) => i.hash(state),
}
}
}
impl PartialEq for ResourceID {
fn eq(&self, other: &Self) -> bool {
let this = match self {
ResourceID::Material(i) => i,
ResourceID::Mesh(i) => i,
};
let that = match other {
ResourceID::Material(i) => i,
ResourceID::Mesh(i) => i,
};
this == that
self.id() == other.id()
}
}
@ -47,6 +53,7 @@ impl<'a> ResourceManager {
Self {
meshes: HashMap::new(),
materials: HashMap::new(),
lights: HashMap::new(),
}
}
@ -62,6 +69,12 @@ impl<'a> ResourceManager {
ResourceID::Mesh(id)
}
pub fn add_light(&mut self, t: Omni) -> ResourceID {
let id = t.id;
self.lights.insert(id, t);
ResourceID::Light(id)
}
pub fn material(&'a self, id: &ResourceID) -> Option<&'a Material> {
if let ResourceID::Material(i) = id {
return Some(self.materials.get(&i).unwrap());
@ -75,12 +88,29 @@ impl<'a> ResourceManager {
}
return None
}
pub fn light(&'a self, id: &ResourceID) -> Option<&'a Omni> {
if let ResourceID::Light(i) = id {
return Some(self.lights.get(&i).unwrap());
}
return None
}
pub fn light_mut(&'a mut self, id: &ResourceID) -> Option<&'a mut Omni> {
if let ResourceID::Light(i) = id {
return Some(self.lights.get_mut(&i).unwrap());
}
return None
}
}
pub trait Renderable {
fn render_data(&self) -> Option<(ResourceID, ResourceID, &cgm::Matrix4<f32>)> {
None
}
fn light_data(&self) -> Option<ResourceID> {
None
}
}
pub struct Object {
@ -95,3 +125,12 @@ impl Renderable for Object {
}
}
pub struct Light {
pub light: ResourceID,
}
impl Renderable for Light {
fn light_data(&self) -> Option<ResourceID> {
Some(self.light)
}
}

View File

@ -9,18 +9,18 @@ use cgmath as cgm;
#[derive(Default, Copy, Clone)]
pub struct Vertex {
pos: [f32; 3],
color: [f32; 3],
normal: [f32; 3],
tex: [f32; 2],
}
impl Vertex {
pub fn new(pos: [f32; 3], color: [f32; 3], tex: [f32; 2]) -> Self {
pub fn new(pos: [f32; 3], normal: [f32; 3], tex: [f32; 2]) -> Self {
Self {
pos, color, tex,
pos, normal, tex,
}
}
}
vulkano::impl_vertex!(Vertex, pos, color, tex);
vulkano::impl_vertex!(Vertex, pos, normal, tex);
#[derive(Default, Copy, Clone)]
pub struct Instance {
@ -37,9 +37,17 @@ impl Instance {
}
vulkano::impl_vertex!(Instance, model);
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug)]
pub struct OmniLight {
pub pos: [f32; 4],
pub color: [f32; 4],
}
#[derive(Copy, Clone, Debug)]
pub struct UniformBufferObject {
pub view: cgm::Matrix4<f32>,
pub camera_pos: cgm::Vector4<f32>,
pub omni_lights: [OmniLight; 3],
}
#[derive(Clone)]

View File

@ -148,6 +148,7 @@ impl<WT: 'static + Send + Sync> Instance<WT> {
fn make_graphics_commands(
&mut self,
profiler: &mut Profiler,
camera: &cgm::Point3<f32>,
view: &cgm::Matrix4<f32>,
rm: &renderable::ResourceManager,
renderables: &Vec<Box<dyn renderable::Renderable>>,
@ -160,31 +161,43 @@ impl<WT: 'static + Send + Sync> Instance<WT> {
0.1,
1000.0
);
let mut buffers: Vec<Box<vc::AutoCommandBuffer>> = vec![];
// Sort renderables by mesh and materialid.
let mut meshes: ResourceMap<(renderable::ResourceID, renderable::ResourceID), &cgm::Matrix4<f32>> = ResourceMap::new();
let ubo = data::UniformBufferObject {
view: proj * view,
};
profiler.end("mgc.prep");
// Sort renderables by mesh and materialid, and find lights.
let mut meshes: ResourceMap<(renderable::ResourceID, renderable::ResourceID), &cgm::Matrix4<f32>> = ResourceMap::new();
let mut omni_lights = [data::OmniLight{ pos: [0.0, 0.0, 0.0, 0.0], color: [0.0, 0.0, 0.0, 0.0]}; 3];
let mut omni_light_count = 0;
for r in renderables {
if let Some((mesh_id, material_id, transform)) = r.render_data() {
meshes.add((mesh_id, material_id), transform);
}
if let Some(light_id) = r.light_data() {
if omni_light_count < 3 {
let light = rm.light(&light_id).unwrap();
omni_lights[omni_light_count] = light.vulkan_uniform();
omni_light_count += 1;
}
}
}
profiler.end("mgc.sort");
let device = self.surface_binding().device.clone();
let queue = self.surface_binding().graphics_queue.clone();
let rp = self.swapchain_binding().render_pass.clone();
let pipeline = self.pipeline.as_ref().unwrap().get_pipeline().clone();
let camera_pos = camera.to_homogeneous();
let ubo = data::UniformBufferObject {
camera_pos: camera_pos,
view: proj * view,
omni_lights,
};
for ((mesh_id, material_id), transforms) in meshes.resources {
let mesh = rm.mesh(&mesh_id).unwrap();
let material = rm.material(&material_id).unwrap();
@ -240,6 +253,7 @@ impl<WT: 'static + Send + Sync> Instance<WT> {
// (╯°□°)╯︵ ┻━┻
pub fn flip(
&mut self,
camera: &cgm::Point3<f32>,
view: &cgm::Matrix4<f32>,
rm: &renderable::ResourceManager,
renderables: &Vec<Box<dyn renderable::Renderable>>,
@ -247,7 +261,7 @@ impl<WT: 'static + Send + Sync> Instance<WT> {
let mut profiler = Profiler::new();
// Build batch command buffer as early as possible.
let mut batches = self.make_graphics_commands(&mut profiler, view, rm, renderables);
let mut batches = self.make_graphics_commands(&mut profiler, camera, view, rm, renderables);
profiler.end("mgc");
match &self.previous_frame_end {
@ -258,7 +272,7 @@ impl<WT: 'static + Send + Sync> Instance<WT> {
if !self.armed {
self.arm();
// Rearming means the batch is invalid - rebuild it.
batches = self.make_graphics_commands(&mut profiler, view, rm, renderables);
batches = self.make_graphics_commands(&mut profiler, camera, view, rm, renderables);
}
profiler.end("arm");

View File

@ -37,11 +37,15 @@ impl Forward {
name: Some(Cow::Borrowed("pos")),
},
vps::ShaderInterfaceDefEntry {
location: 1..5, format: Format::R32G32B32A32Sfloat,
location: 1..2, format: Format::R32G32B32Sfloat,
name: Some(Cow::Borrowed("normal")),
},
vps::ShaderInterfaceDefEntry {
location: 2..6, format: Format::R32G32B32A32Sfloat,
name: Some(Cow::Borrowed("model")),
},
vps::ShaderInterfaceDefEntry {
location: 5..6, format: Format::R32G32Sfloat,
location: 6..7, format: Format::R32G32Sfloat,
name: Some(Cow::Borrowed("tex")),
},
],
@ -50,14 +54,23 @@ impl Forward {
location: 0..1, format: Format::R32G32Sfloat,
name: Some(Cow::Borrowed("fragTexCoord")),
},
vps::ShaderInterfaceDefEntry {
location: 1..2, format: Format::R32G32B32Sfloat,
name: Some(Cow::Borrowed("fragWorldPos")),
},
vps::ShaderInterfaceDefEntry {
location: 2..3, format: Format::R32G32B32Sfloat,
name: Some(Cow::Borrowed("fragNormal")),
},
],
uniforms: vec![],
push_constants: vec![
vdp::PipelineLayoutDescPcRange {
offset: 0,
size: 64usize,
size: 128usize,
stages: vdD::ShaderStages {
vertex: true,
fragment: true,
..vdD::ShaderStages::none()
},
},
@ -72,6 +85,14 @@ impl Forward {
location: 0..1, format: Format::R32G32Sfloat,
name: Some(Cow::Borrowed("fragTexCoord")),
},
vps::ShaderInterfaceDefEntry {
location: 1..2, format: Format::R32G32B32Sfloat,
name: Some(Cow::Borrowed("fragWorldPos")),
},
vps::ShaderInterfaceDefEntry {
location: 2..3, format: Format::R32G32B32Sfloat,
name: Some(Cow::Borrowed("fragNormal")),
},
],
outputs: vec![
vps::ShaderInterfaceDefEntry {
@ -111,7 +132,17 @@ impl Forward {
},
},
],
push_constants: vec![],
push_constants: vec![
vdp::PipelineLayoutDescPcRange {
offset: 0,
size: 128usize,
stages: vdD::ShaderStages {
vertex: true,
fragment: true,
..vdD::ShaderStages::none()
},
},
],
}.load_into(device.clone()).expect("could not load fragment shader");
let dimensions = [viewport_dimensions[0] as f32, viewport_dimensions[1] as f32];