struct Params {
x_mul: f32,
x_add: f32,
y_mul: f32,
y_add: f32,
}
@group(0) @binding(0)
var t_a: texture_2d<f32>;
@group(0) @binding(1)
var t_n: texture_2d<f32>;
@group(0) @binding(2)
var t_v: texture_2d<f32>;
@group(0) @binding(3)
var s: sampler;
@group(0) @binding(4)
var<uniform> param: Params;
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
}
const pos = array(vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));
@vertex
fn vs_main(
@builtin(vertex_index) index: u32,
) -> VertexOutput {
var out: VertexOutput;
out.clip_position = vec4<f32>(pos[index], 0.0, 1.0);
return out;
}
const PI: f32 = 3.14159265359;
const F0 = vec3<f32>(0.04, 0.04, 0.04);
const UP = vec3<f32>(0., 1., 0.);
const RIGHT = vec3<f32>(-1., 0., 0.);
const FORWARD = vec3<f32>(0., 0., -1.);
const LIGHT_DIR = (RIGHT - UP - FORWARD) / sqrt(3.);
const LIGHT_DIR_2 = (-RIGHT + UP - FORWARD) / sqrt(3.);
const LIGHT_DIR_3 = (RIGHT + UP + FORWARD) / sqrt(3.);
// PBR functions taken from https://learnopengl.com/PBR/Theory
fn DistributionGGX(N: vec3<f32>, H: vec3<f32>, a: f32) -> f32 {
let a2 = a * a;
let NdotH = max(dot(N, H), 0.0);
let NdotH2 = NdotH * NdotH;
let nom = a2;
var denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = denom * denom;
return nom / denom;
}
fn GeometrySchlickGGX(NdotV: f32, k: f32) -> f32 {
let nom = NdotV;
let denom = NdotV * (1.0 - k) + k;
return nom / denom;
}
fn fresnelSchlick(cosTheta: f32) -> vec3<f32> {
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
fn normalized_view_from_screen_coord(coord: vec2<f32>) -> vec3<f32> {
// reconstruct view-space position from the screen coordinate and view space depth.
return normalize(vec3<f32>(
vec2<f32>(param.x_mul, param.y_mul) * coord + vec2<f32>(param.x_add, param.y_add),
1.
));
}
const K_S = 0.8 - F0.x;
const K_D = 0.15;
const ONE_4F0 = 1. / (4. * F0.x);
//Khronos PBR neutral
fn tone_map(rgb: vec3<f32>) -> vec3<f32> {
let x = min(min(rgb.x, rgb.y), rgb.z);
let f = select(F0.x, x - x * x * ONE_4F0, x <= 2. * F0.x);
let color = rgb - vec3<f32>(f);
let p = max(max(color.x, color.y), color.z);
let p_n = 1. - ((1. - K_S) * (1. - K_S)) / (p + 1. - 2. * K_S);
let g = 1. / (K_D * (p - p_n) + 1.);
let res = select(
color,
mix(vec3<f32>(p_n), color * p_n / p, g),
p > K_S);
return res;
}
@fragment
fn fs_main(@builtin(position) fcoords: vec4<f32>) -> @location(0) vec4<f32> {
let coords = vec2<i32>(fcoords.xy);
let albedo = textureLoad(t_a, coords, 0);
if albedo.w < 0.01 {
discard;
}
let buffer_size = textureDimensions(t_a);
let view_dir = normalized_view_from_screen_coord(fcoords.xy / vec2<f32>(buffer_size));
let normal = normalize(textureLoad(t_n, coords, 0).xyz * 2. - vec3<f32>(1.));
let visibility = textureLoad(t_v, coords, 0).x;
let kd = 1. * albedo.xyz;
let k = albedo.w;
let n_dot_v = max(dot(normal, view_dir), 0.);
let f = fresnelSchlick(dot(view_dir, normal));
let ggx1 = GeometrySchlickGGX(n_dot_v, k);
let f_ct_fact = select(vec3<f32>(0.), ggx1 * f / (4. * n_dot_v), n_dot_v > 0.);
let half_dir = normalize(view_dir + LIGHT_DIR);
let d = DistributionGGX(normal, half_dir, k);
let ggx2 = GeometrySchlickGGX(max(dot(view_dir, LIGHT_DIR), 0.), k);
let f_ct = d * f_ct_fact * ggx2;
var result = 0.55 * (kd * max(dot(normal, LIGHT_DIR), 0.0) + f_ct);
let half_dir_2 = normalize(view_dir + LIGHT_DIR_2);
let d2 = DistributionGGX(normal, half_dir_2, k);
let ggx2_2 = GeometrySchlickGGX(max(dot(view_dir, LIGHT_DIR_2), 0.), k);
let f_ct_2 = d2 * f_ct_fact * ggx2_2;
result += 1.6 * (kd * max(dot(normal, LIGHT_DIR_2), 0.0) + f_ct_2);
let half_dir_3 = normalize(view_dir + LIGHT_DIR_3);
let d3 = DistributionGGX(normal, half_dir_3, k);
let ggx2_3 = GeometrySchlickGGX(max(dot(view_dir, LIGHT_DIR_3), 0.), k);
let f_ct_3 = d3 * f_ct_fact * ggx2_3;
result += 1.4 * (kd * max(dot(normal, LIGHT_DIR_3), 0.0) + f_ct_3);
result *= 1.2 * visibility;
return vec4<f32>(tone_map(result), 1.0);
}