Include the AcerolaBRDF

This commit is contained in:
2025-07-11 20:32:00 +01:00
parent 172f549e5b
commit 1f2c3f4062
9 changed files with 318 additions and 28 deletions
+135
View File
@@ -0,0 +1,135 @@
#define PI 3.14159265
float sqr(float x) {
return x * x;
}
float luminance(vec3 color) {
return dot(color, vec3(0.299, 0.587, 0.114));
}
float SchlickFresnel(float x) {
x = clamp(1.0f - x, 0., 1.);
float x2 = x * x;
return x2 * x2 * x; // While this is equivalent to pow(1 - x, 5) it is two less mult instructions
}
// Isotropic Generalized Trowbridge Reitz with gamma == 1
float GTR1(float ndoth, float a) {
float a2 = a * a;
float t = 1.0 + (a2 - 1.0) * ndoth * ndoth;
return (a2 - 1.0) / (PI * log(a2) * t);
}
// Anisotropic Generalized Trowbridge Reitz with gamma == 2. This is equal to the popular GGX distribution.
float AnisotropicGTR2(float ndoth, float hdotx, float hdoty, float ax, float ay) {
return 1 / (PI * ax * ay * sqr(sqr(hdotx / ax) + sqr(hdoty / ay) + sqr(ndoth)));
}
// Isotropic Geometric Attenuation Function for GGX. This is technically different from what Disney uses, but it's basically the same.
float SmithGGX(float alphaSquared, float ndotl, float ndotv) {
float a = ndotv * sqrt(alphaSquared + ndotl * (ndotl - alphaSquared * ndotl));
float b = ndotl * sqrt(alphaSquared + ndotv * (ndotv - alphaSquared * ndotv));
return 0.5 / (a + b);
}
// Anisotropic Geometric Attenuation Function for GGX.
float AnisotropicSmithGGX(float ndots, float sdotx, float sdoty, float ax, float ay) {
return 1 / (ndots + sqrt(sqr(sdotx * ax) + sqr(sdoty * ay) + sqr(ndots)));
}
struct BRDFInput {
vec3 L; // Direction to light source
vec3 V; // Direction to camera/viewer
vec3 N; // World space normal (unpacked from normal map)
//vec3 X; // World space tangent (unpacked from tangent map)
//vec3 Y; // World space bitangent
vec3 base_color;
float roughness; // From uniform or texture map
float specular;
float specular_tint;
float sheen_tint;
float metallic;
//float anisotropic;
float sheen;
float clear_coat_gloss;
float subsurface;
float clear_coat;
};
struct BRDFResults {
vec3 diffuse;
vec3 specular;
vec3 clearcoat;
};
BRDFResults DisneyBRDF(BRDFInput i) {
BRDFResults op;
op.diffuse = vec3(0.0);
op.specular = vec3(0.0);
op.clearcoat = vec3(0.0);
vec3 H = normalize(i.L + i.V); // Microfacet normal of perfect reflection
float ndotl = clamp(dot(i.N, i.L), -1., 1.);
float ndotv = clamp(dot(i.N, i.V), -1., 1.);
float ndoth = clamp(dot(i.N, H), -1., 1.);
float ldoth = clamp(dot(i.L, H), -1., 1.);
float Cdlum = luminance(i.base_color);
vec3 Ctint = Cdlum > 0.0 ? i.base_color / Cdlum : vec3(1.0);
vec3 Cspec0 = mix(i.specular * 0.08 * mix(vec3(1.0), Ctint, i.specular_tint), i.base_color * (1.0 + i.specular), i.metallic);
vec3 Csheen = mix(vec3(1.0), Ctint, i.sheen_tint);
// Disney Diffuse
float FL = SchlickFresnel(ndotl);
float FV = SchlickFresnel(ndotv);
float Fss90 = ldoth * ldoth * i.roughness;
float Fd90 = 0.5 + 2.0 * Fss90;
float Fd = mix(1.0, Fd90, FL) * mix(1.0, Fd90, FV);
// Subsurface Diffuse (Hanrahan-Krueger brdf approximation)
float Fss = mix(1.0, Fss90, FL) * mix(1.0, Fss90, FV);
float ss = 1.25 * (Fss * ((1. / (ndotl + ndotv)) - 0.5) + 0.5);
// Specular
float alpha = i.roughness;
float alphaSquared = alpha * alpha;
//// Anisotropic Microfacet Normal Distribution (Normalized Anisotropic GTR gamma == 2)
//float aspectRatio = sqrt(1.0 - i.anisotropic * 0.9);
//float alphaX = max(0.001, alphaSquared / aspectRatio);
//float alphaY = max(0.001, alphaSquared * aspectRatio);
//float Ds = AnisotropicGTR2(ndoth, dot(H, i.X), dot(H, i.Y), alphaX, alphaY);
//// Geometric Attenuation
//float GalphaSquared = sqr(0.5 + i.roughness * 0.5);
//float GalphaX = max(0.001, GalphaSquared / aspectRatio);
//float GalphaY = max(0.001, GalphaSquared * aspectRatio);
//float G = AnisotropicSmithGGX(ndotl, dot(i.L, i.X), dot(i.L, i.Y), GalphaX, GalphaY);
//G *= AnisotropicSmithGGX(ndotv, dot(i.V, i.X), dot(i.V, i.Y), GalphaX, GalphaY); // specular brdf denominator (4 * ndotl * ndotv) is baked into output here (I assume at least)
// Fresnel Reflectance
float FH = SchlickFresnel(ldoth);
vec3 F = mix(Cspec0, vec3(1.0), FH);
// Sheen
vec3 Fsheen = mix(Cspec0, vec3(1.0), SchlickFresnel(ndotv)) * i.sheen * Csheen;
// Clearcoat (Hard Coded Index Of Refraction -> 1.5f -> F0 -> 0.04)
float Dr = GTR1(ndoth, mix(0.1, 0.001f, i.clear_coat_gloss)); // Normalized Isotropic GTR Gamma == 1
float Fr = mix(0.04, 1.0f, FH);
float Gr = SmithGGX(ndotl, ndotv, 0.25);
op.diffuse = (1.0 / PI) * (mix(Fd, ss, i.subsurface) * i.base_color + Fsheen) * (1 - i.metallic) * (1 - F);
op.specular = F; //clamp(Ds * F * G, 0.0, 1.0);
op.clearcoat = vec3(clamp(0.25 * i.clear_coat * Gr * Fr * Dr, 0.0, 1.0));
return op;
}
+56
View File
@@ -21,6 +21,7 @@ pub(crate) struct PreviousDebug {
pub(crate) show_normals: bool,
pub(crate) show_depth: bool,
pub(crate) show_albedo: bool,
pub(crate) show_material: bool,
pub(crate) show_all: bool,
pub(crate) disable_ao: bool,
}
@@ -37,6 +38,14 @@ pub(crate) struct GState {
pub(crate) fps: [f64; 128],
pub(crate) debug: PreviousDebug,
pub(crate) subsurface: f32,
pub(crate) specular: f32,
pub(crate) specular_tint: f32,
pub(crate) sheen: f32,
pub(crate) sheen_tint: f32,
pub(crate) clear_coat_gloss: f32,
pub(crate) clear_coat: f32,
}
impl Default for GState {
@@ -52,6 +61,14 @@ impl Default for GState {
fps: [0.0; 128],
debug: Default::default(),
specular: 0.73,
specular_tint: 0.,
sheen_tint: 0.,
sheen: 0.58,
clear_coat_gloss: 0.63,
subsurface: 0.,
clear_coat: 0.85,
}
}
}
@@ -81,6 +98,26 @@ pub(crate) fn gui_up(gui: &mut Gui, state: &mut GState, new_csg_needed: &mut boo
egui::Slider::new(&mut state.move_speed, 0.0..=2.0)
.text("Movement Speed"),
);
ui.heading("Material");
ui.add(
egui::Slider::new(&mut state.subsurface, 0.0..=1.0).text("Subsurface"),
);
ui.add(egui::Slider::new(&mut state.specular, 0.0..=1.0).text("Specular"));
ui.add(
egui::Slider::new(&mut state.specular_tint, 0.0..=1.0)
.text("Specular tint"),
);
ui.add(egui::Slider::new(&mut state.sheen, 0.0..=1.0).text("Sheen"));
ui.add(
egui::Slider::new(&mut state.sheen_tint, 0.0..=1.0).text("Sheen tint"),
);
ui.add(
egui::Slider::new(&mut state.clear_coat, 0.0..=1.0).text("Clear Coat"),
);
ui.add(
egui::Slider::new(&mut state.clear_coat_gloss, 0.0..=1.0)
.text("Clear Coat Gloss"),
);
ui.heading(format!("Meshes ({})", state.meshes.len()));
let mut meshdel = vec![];
for mesh in &mut state.meshes {
@@ -148,6 +185,21 @@ pub(crate) fn gui_up(gui: &mut Gui, state: &mut GState, new_csg_needed: &mut boo
ui.add(egui::Slider::new(&mut csg.scale.x, 0.0..=2.0).text("Scale.x"));
ui.add(egui::Slider::new(&mut csg.scale.y, 0.0..=2.0).text("Scale.y"));
ui.add(egui::Slider::new(&mut csg.scale.z, 0.0..=2.0).text("Scale.z"));
ui.add(
egui::Slider::new(&mut csg.colour.x, 0.0..=1.0).text("Colour.r"),
);
ui.add(
egui::Slider::new(&mut csg.colour.y, 0.0..=1.0).text("Colour.g"),
);
ui.add(
egui::Slider::new(&mut csg.colour.z, 0.0..=1.0).text("Colour.b"),
);
ui.add(
egui::Slider::new(&mut csg.roughness, 0.0..=1.0).text("Roughness"),
);
ui.add(
egui::Slider::new(&mut csg.metallic, 0.0..=1.0).text("Metallic"),
);
ui.horizontal(|ui| {
ui.label("Subdivision");
for x in MINUMUM_SUBDIVISION.ilog2()..=MAXIMUM_SUBDIVISION.ilog2() {
@@ -249,6 +301,10 @@ pub(crate) fn gui_up(gui: &mut Gui, state: &mut GState, new_csg_needed: &mut boo
&mut state.debug.show_albedo,
"Display the albedo render attachment",
);
ui.toggle_value(
&mut state.debug.show_material,
"Display the material render attachment",
);
ui.toggle_value(
&mut state.debug.show_all,
"Display all render attachments",
+3
View File
@@ -11,6 +11,9 @@ layout(set = 1, binding = 0) uniform Object {
uint material;
uint subdivision_log2;
float farplane;
vec3 colour;
float metallic;
float roughness;
} object_uniforms;
float scene(vec4 p) {
+11 -2
View File
@@ -7,8 +7,9 @@ layout(constant_id = 2) const bool SPEC_SHOW_AO = false;
layout(constant_id = 3) const bool SPEC_SHOW_NORMALS = false;
layout(constant_id = 4) const bool SPEC_SHOW_DEPTH = false;
layout(constant_id = 5) const bool SPEC_SHOW_ALBEDO = false;
layout(constant_id = 6) const bool SPEC_SHOW_ALL = false;
layout(constant_id = 7) const bool SPEC_DISABLE_AO = false;
layout(constant_id = 6) const bool SPEC_SHOW_MATERIAL = false;
layout(constant_id = 7) const bool SPEC_SHOW_ALL = false;
layout(constant_id = 8) const bool SPEC_DISABLE_AO = false;
//layout(constant_id = 30) const uint SPEC_MAXIMUM_SUBDIVISION = 64;
const uint SPEC_MAXIMUM_SUBDIVISION = 64;
@@ -28,6 +29,14 @@ layout(set = 0, binding = 1) uniform Camera {
mat4 proj_view;
mat4 inv_proj_view;
vec4 campos_and_time;
float specular;
float specular_tint;
float sheen_tint;
float sheen;
float clear_coat_gloss;
float subsurface;
float clear_coat;
} camera_uniforms;
#endif
+36 -9
View File
@@ -1,11 +1,13 @@
#version 460
#extension GL_GOOGLE_include_directive:require
#include "include.glsl"
#include "brdf.glsl"
layout(input_attachment_index = 0, set = 1, binding = 0) uniform subpassInput u_color;
layout(input_attachment_index = 1, set = 1, binding = 1) uniform subpassInput u_normals;
layout(input_attachment_index = 2, set = 1, binding = 2) uniform subpassInput u_ao_r_m;
layout(input_attachment_index = 3, set = 1, binding = 3) uniform subpassInput u_depth;
layout(input_attachment_index = 4, set = 1, binding = 4) uniform usubpassInput u_material;
layout(location = 0) in vec2 v_pos;
layout(location = 0) out vec4 f_color;
@@ -16,17 +18,36 @@ vec3 calculate_view_position(float depth)
return (view_position.xyz / view_position.w);
}
vec3 shading(vec3 albedo, vec3 normal, vec3 position, float ao)
vec3 shading(vec3 albedo, vec3 normal, vec3 position, vec3 ao_r_m)
{
vec3 accum = vec3(0., 0., 0.);
BRDFInput inp;
inp.base_color = albedo;
inp.N = normal;
inp.V = normalize(camera_uniforms.campos_and_time.xyz - position);
inp.roughness = ao_r_m.y;
inp.specular = camera_uniforms.specular;
inp.specular_tint = camera_uniforms.specular_tint;
inp.sheen_tint = camera_uniforms.sheen_tint;
inp.metallic = ao_r_m.z;
inp.sheen = camera_uniforms.sheen;
inp.clear_coat_gloss = camera_uniforms.clear_coat_gloss;
inp.subsurface = camera_uniforms.subsurface;
inp.clear_coat = camera_uniforms.clear_coat;
BRDFResults r;
for (int i = 0; (i < light_uniforms.light_count) && (i < 32); i++)
{
accum += light_uniforms.col[i].xyz * ((dot(normal, normalize(light_uniforms.pos[i].xyz - position)) * .5) + .5);
inp.L = normalize(light_uniforms.pos[i].xyz - position);
r = DisneyBRDF(inp);
vec3 outp = light_uniforms.col[i].xyz * (r.diffuse + r.specular + r.clearcoat);
outp *= clamp(dot(inp.N, inp.L), -1., 1.);
accum += max(vec3(0.0), outp);
}
//return vec3(int(position.x * 256.0) ^ int(position.y * 256.0) ^ int(position.z * 256.0));
return albedo * accum * ao;
return accum * ao_r_m.x;
}
void main() {
@@ -35,9 +56,11 @@ void main() {
} else if (SPEC_SHOW_NORMALS) {
f_color = subpassLoad(u_normals);
} else if (SPEC_SHOW_DEPTH) {
f_color = subpassLoad(u_depth);
f_color = subpassLoad(u_depth) * 100.0;
} else if (SPEC_SHOW_ALBEDO) {
f_color = subpassLoad(u_color);
} else if (SPEC_SHOW_MATERIAL) {
f_color = subpassLoad(u_material) / 16.;
} else {
float depth = subpassLoad(u_depth).x;
if (depth == 1.0 && !SPEC_SHOW_ALL) {
@@ -45,12 +68,13 @@ void main() {
}
vec3 albedo = subpassLoad(u_color).xyz;
vec3 normal = subpassLoad(u_normals).xyz;
float ao = subpassLoad(u_ao_r_m).x;
vec3 ao_r_m = subpassLoad(u_ao_r_m).xyz;
vec3 position = calculate_view_position(depth);
vec3 shading = shading(albedo, normal, position, ao);
uint material = subpassLoad(u_material).x;
vec3 shading = shading(albedo, normal, position, ao_r_m);
if (SPEC_SHOW_ALL) {
switch (uint(floor((v_pos.x + 1.0) * (5.0 / 2.0)))) {
switch (uint(floor((v_pos.x + 1.0) * (6.0 / 2.0)))) {
default:
case 0:
f_color = vec4(shading, 1.0);
@@ -62,11 +86,14 @@ void main() {
f_color = subpassLoad(u_normals);
break;
case 3:
f_color = subpassLoad(u_depth);
f_color = subpassLoad(u_depth) * 100.0;
break;
case 4:
f_color = subpassLoad(u_color);
break;
case 5:
f_color = subpassLoad(u_material) / 16.;
break;
}
} else {
f_color = vec4(shading, 1.0);
+16 -3
View File
@@ -743,7 +743,7 @@ impl ApplicationHandler for App {
{
color: [final_color],
depth_stencil: {},
input: [color, normals, ao_r_m, depth],
input: [color, normals, ao_r_m, depth, material],
},
{
color: [final_color],
@@ -1028,6 +1028,13 @@ impl App {
self.cstate.position.z,
self.time,
],
specular: self.gstate.specular.into(),
specular_tint: self.gstate.specular_tint.into(),
sheen_tint: self.gstate.sheen_tint.into(),
sheen: self.gstate.sheen.into(),
clear_coat_gloss: self.gstate.clear_coat_gloss.into(),
subsurface: self.gstate.subsurface.into(),
clear_coat: self.gstate.clear_coat.into(),
};
if self.cstate.looking {
@@ -1179,6 +1186,7 @@ impl App {
WriteDescriptorSet::image_view(1, rcx.normal_buffer.clone()),
WriteDescriptorSet::image_view(2, rcx.ao_r_m_buffer.clone()),
WriteDescriptorSet::image_view(3, rcx.depth_buffer.clone()),
WriteDescriptorSet::image_view(4, rcx.material_buffer.clone()),
],
[],
)
@@ -1841,8 +1849,9 @@ fn get_spec_constants(debug: &PreviousDebug) -> HashMap<u32, SpecializationConst
const SPEC_SHOW_NORMALS: u32 = 3;
const SPEC_SHOW_DEPTH: u32 = 4;
const SPEC_SHOW_ALBEDO: u32 = 5;
const SPEC_SHOW_ALL: u32 = 6;
const SPEC_DISABLE_AO: u32 = 7;
const SPEC_SHOW_MATERIAL: u32 = 6;
const SPEC_SHOW_ALL: u32 = 7;
const SPEC_DISABLE_AO: u32 = 8;
const SPEC_MAXIMUM_SUBDISIVION: u32 = 30;
let mut spec_constants = HashMap::with_capacity(6);
@@ -1867,6 +1876,10 @@ fn get_spec_constants(debug: &PreviousDebug) -> HashMap<u32, SpecializationConst
SPEC_SHOW_ALBEDO,
SpecializationConstant::Bool(debug.show_albedo),
);
spec_constants.insert(
SPEC_SHOW_MATERIAL,
SpecializationConstant::Bool(debug.show_material),
);
spec_constants.insert(SPEC_SHOW_ALL, SpecializationConstant::Bool(debug.show_all));
spec_constants.insert(
SPEC_DISABLE_AO,
+24 -1
View File
@@ -30,6 +30,26 @@ vec3 getNormal(vec3 p) {
return normalize(n - (scene(vec4(p, camera_uniforms.campos_and_time.w))));
}
vec3 getTangent(vec3 p, vec3 n) {
vec3 rayori = p;
rayori.x += 0.01;
vec3 raypos = rayori;
n *= -1;
float total_dist = 0.;
for (uint i = 0; i < 10; i++) {
float dist = scene(vec4(raypos, camera_uniforms.campos_and_time.w));
total_dist += dist;
raypos = fma(n, vec3(total_dist), rayori);
}
return normalize(raypos - p);
}
vec3 getBiTangent(vec3 n, vec3 t) {
return normalize(cross(n, t));
}
const float AO_INSTENSITY = 0.1;
const float AO_STEP = 0.01;
const uint AO_MAX_ITER = 10;
@@ -58,7 +78,10 @@ void main() {
vec3 n = getNormal(position);
f_color = vec4(0.5, 0.5, 0.5, 1.0);
f_color = vec4(1.0);
f_ao_r_m.y = object_uniforms.roughness;
f_ao_r_m.z = object_uniforms.metallic;
mat3 rotation = mat3(pc.world[0].xyz, pc.world[1].xyz, pc.world[2].xyz);
f_normal = vec4(normalize(rotation * n), 1.0);
+3
View File
@@ -43,6 +43,9 @@ pub(crate) struct CSG {
pub(crate) pos: Vec3,
pub(crate) rot: Vec3,
pub(crate) scale: Vec3,
pub(crate) colour: Vec3,
pub(crate) roughness: f32,
pub(crate) metallic: f32,
pub(crate) new_pipelines_needed: bool,
pub(crate) trace_shader_module: Arc<ShaderModule>,
pub(crate) normals_shader_module: Arc<ShaderModule>,
+30 -9
View File
@@ -11,19 +11,30 @@ use glam::{EulerRot, Mat4, Vec3};
use log::info;
use rspirv::{binary::Assemble, dr::Module};
use vulkano::{
buffer::{allocator::SubbufferAllocator, Subbuffer},
command_buffer::{allocator::StandardCommandBufferAllocator, CommandBufferExecFuture},
buffer::{Subbuffer, allocator::SubbufferAllocator},
command_buffer::{CommandBufferExecFuture, allocator::StandardCommandBufferAllocator},
descriptor_set::{
allocator::StandardDescriptorSetAllocator, DescriptorSet, WriteDescriptorSet
DescriptorSet, WriteDescriptorSet, allocator::StandardDescriptorSetAllocator,
},
device::{Device, Queue},
pipeline::{
cache::PipelineCache, graphics::{
color_blend::{ColorBlendAttachmentState, ColorBlendState}, depth_stencil::{DepthState, DepthStencilState}, input_assembly::InputAssemblyState, multisample::MultisampleState, rasterization::{CullMode, FrontFace, PolygonMode, RasterizationState}, vertex_input::{Vertex, VertexDefinition, VertexInputState}, GraphicsPipelineCreateInfo
}, layout::PipelineDescriptorSetLayoutCreateInfo, DynamicState, GraphicsPipeline, Pipeline, PipelineCreateFlags, PipelineLayout, PipelineShaderStageCreateInfo
DynamicState, GraphicsPipeline, Pipeline, PipelineCreateFlags, PipelineLayout,
PipelineShaderStageCreateInfo,
cache::PipelineCache,
graphics::{
GraphicsPipelineCreateInfo,
color_blend::{ColorBlendAttachmentState, ColorBlendState},
depth_stencil::{DepthState, DepthStencilState},
input_assembly::InputAssemblyState,
multisample::MultisampleState,
rasterization::{CullMode, FrontFace, PolygonMode, RasterizationState},
vertex_input::{Vertex, VertexDefinition, VertexInputState},
},
layout::PipelineDescriptorSetLayoutCreateInfo,
},
render_pass::{RenderPass, Subpass},
shader::{ShaderModule, ShaderModuleCreateInfo}, sync::future::NowFuture,
shader::{ShaderModule, ShaderModuleCreateInfo},
sync::future::NowFuture,
};
use crate::{
@@ -190,6 +201,9 @@ pub fn thread_loop(recv: mpmc::Receiver<WorkItem>, send: mpsc::SyncSender<WorkCo
enable_buffer_host_visible,
trace_descriptor_set,
normals_descriptor_set,
colour: Vec3::ONE,
roughness: 1.0,
metallic: 0.67,
}));
send.send(WorkComplete::CreateCSG(csg, index)).unwrap();
@@ -233,7 +247,11 @@ pub fn thread_loop(recv: mpmc::Receiver<WorkItem>, send: mpsc::SyncSender<WorkCo
transfer_queue,
);
send.send(WorkComplete::GetPushConstants(push_constants, future, index))
send.send(WorkComplete::GetPushConstants(
push_constants,
future,
index,
))
.unwrap();
},
WorkItem::RecompilePipelines(
@@ -519,7 +537,10 @@ fn interval_check(
(MAXIMUM_SUBDIVISION * MAXIMUM_SUBDIVISION * MAXIMUM_SUBDIVISION) as usize / (32 * 4)],
material,
subdivision_log2: csg.subdivision.ilog2(),
farplane: (highest_corner - lowest_corner).length() / csg.subdivision as f32,
farplane: ((highest_corner - lowest_corner).length() / csg.subdivision as f32).into(),
colour: csg.colour.to_array().into(),
roughness: csg.roughness,
metallic: csg.metallic,
};
for x in 0..xdim {