Fix PBR equations, add fake IBL

This commit is contained in:
sergeypdev 2024-08-03 17:41:53 +04:00
parent 37f603dc8a
commit bf4f6a5fc2
5 changed files with 86 additions and 46 deletions

BIN
assets/ibl_brdf_lut.norm.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -25,7 +25,7 @@ layout(std430, binding = 3) readonly buffer DrawCmdDatas {
layout(location = 16, bindless_sampler) uniform sampler2DArrayShadow shadow_maps;
layout(location = 17, bindless_sampler) uniform samplerCubeArrayShadow cube_shadow_maps;
layout(location = 21) uniform uint lights_count;
layout(location = 22, bindless_sampler) uniform sampler2D brdfLut;
// Input, output blocks
@ -35,7 +35,9 @@ VERTEX_EXPORT VertexData {
vec2 uv;
mat3 vTBN;
vec3 wPos;
vec3 vUp;
vec3 wNormal;
vec3 vNormal;
} VertexOut;
VERTEX_EXPORT flat uint DrawID;
@ -59,6 +61,8 @@ void main() {
vec4 vPos = viewModel * vec4(aPos.xyz, 1.0);
gl_Position = projection * vPos;
VertexOut.vUp = normalize(mat3(viewModel) * vec3(0.0, 1.0, 0.0));
VertexOut.vPos = vPos.xyz / vPos.w;
VertexOut.uv = aUV;
vec3 aBitangent = cross(aTangent, aNormal);
@ -68,6 +72,7 @@ void main() {
VertexOut.vTBN = mat3(T, B, N);
VertexOut.wPos = (model * vec4(aPos.xyz, 1.0)).xyz;
VertexOut.wNormal = normalize(model * vec4(aNormal, 0.0)).xyz;
VertexOut.vNormal = normalize(mat3(viewModel) * aNormal).xyz;
}
#endif // VERTEX_SHADER
@ -110,7 +115,7 @@ struct Material {
layout(std430, binding = 1) readonly buffer Lights {
uint _lights_count;
uint lights_count;
Light lights[];
};
@ -123,7 +128,7 @@ out vec4 FragColor;
struct EvalMaterial {
vec4 albedo;
bool metallic;
float metallic;
float roughness;
vec3 emission;
};
@ -152,35 +157,41 @@ EvalMaterial evalMaterial() {
EvalMaterial result;
int materialIdx = draw_data[DrawID].materialIdx;
result.albedo = textureSize(materials[materialIdx].albedo_map, 0) == ivec2(0) ? vec4(pow(materials[materialIdx].albedo.rgb, vec3(2.2)), materials[materialIdx].albedo.a) : texture(materials[materialIdx].albedo_map, VertexOut.uv * materials[materialIdx].albedo_map_uv_scale);
float fMetallic = textureSize(materials[materialIdx].metallic_map, 0) == ivec2(0) ? materials[materialIdx].metallic : texture(materials[materialIdx].metallic_map, VertexOut.uv * materials[materialIdx].metallic_map_uv_scale).b;
result.metallic = fMetallic > 0.1;
result.metallic = textureSize(materials[materialIdx].metallic_map, 0) == ivec2(0) ? materials[materialIdx].metallic : texture(materials[materialIdx].metallic_map, VertexOut.uv * materials[materialIdx].metallic_map_uv_scale).b;
result.roughness = max(0.01, textureSize(materials[materialIdx].roughness_map, 0) == ivec2(0) ? materials[materialIdx].roughness : texture(materials[materialIdx].roughness_map, VertexOut.uv * materials[materialIdx].roughness_map_uv_scale).g);
result.emission = textureSize(materials[materialIdx].emission_map, 0) == ivec2(0) ? materials[materialIdx].emission : texture(materials[materialIdx].emission_map, VertexOut.uv * materials[materialIdx].emission_map_uv_scale).rgb;
return result;
}
vec3 schlickFresnel(EvalMaterial mat, float LDotH) {
vec3 f0 = vec3(0.04); // dielectric
if (mat.metallic) {
f0 = mat.albedo.rgb;
}
vec3 schlickFresnel(EvalMaterial mat, float NDotV) {
vec3 f0 = mix(vec3(0.04), mat.albedo.rgb, mat.metallic);
return f0 + (1 - f0) * pow(1.0 - LDotH, 5);
return f0 + (1.0 - f0) * pow(1.0 - NDotV, 5.0);
}
vec3 schlickFresnelRoughness(EvalMaterial mat, float NDotV) {
vec3 f0 = mix(vec3(0.04), mat.albedo.rgb, mat.metallic);
return f0 + (max(vec3(1.0 - mat.roughness), f0) - f0) * pow(1.0 - NDotV, 5.0);
}
const float eps = 0.0001;
float geomSmith(EvalMaterial mat, float DotVal) {
float k = (mat.roughness + 1.0) * (mat.roughness + 1.0) / 8.0;
float k = ((mat.roughness + 1.0) * (mat.roughness + 1.0)) / 8.0;
float denom = DotVal * (1 - k) + k;
return 1.0 / max(denom, eps);
return DotVal / denom;
}
float ggxDistribution(EvalMaterial mat, float NDotH) {
float alpha2 = mat.roughness * mat.roughness * mat.roughness * mat.roughness;
float d = (NDotH * NDotH) * (alpha2 - 1) + 1;
return alpha2 / max((PI * d * d), eps);
float a = mat.roughness * mat.roughness;
float alpha2 = a * a;
float NDotH2 = NDotH * NDotH;
float nom = alpha2;
float denom = (NDotH2 * (alpha2 - 1.0) + 1.0);
denom = PI * denom * denom;
return nom / denom;
}
float lightAttenuation(bool point, float dist, float radius) {
@ -216,8 +227,8 @@ vec3 microfacetModel(EvalMaterial mat, int light_idx, vec3 P, vec3 N) {
// Light light = lights[light_idx];
vec3 diffuseBrdf = vec3(0); // metallic
if (!mat.metallic) {
diffuseBrdf = mat.albedo.rgb;
if (mat.metallic < 1.0) {
diffuseBrdf = mat.albedo.rgb / PI;
}
// 0 - means directional, 1 - means point light
@ -227,10 +238,9 @@ vec3 microfacetModel(EvalMaterial mat, int light_idx, vec3 P, vec3 N) {
vec3 V = normalize(-P);
vec3 H = normalize(V + L);
float NDotH = dot(N, H);
float LDotH = dot(L, H);
float NDotL = max(dot(N, L), 0);
float NDotV = dot(N, V);
float NDotH = max(dot(N, H), 0.0);
float NDotL = max(dot(N, L), 0.0);
float NDotV = max(dot(N, V), 0.0);
float normal_offset_scale = clamp(1 - NDotL, 0, 1);
normal_offset_scale *= 10; // constant
@ -301,9 +311,34 @@ vec3 microfacetModel(EvalMaterial mat, int light_idx, vec3 P, vec3 N) {
}
shadow_mult = clamp(shadow_mult, 0.0, 1.0);
vec3 specBrdf = 0.25 * ggxDistribution(mat, NDotH) * schlickFresnel(mat, LDotH) * geomSmith(mat, NDotL) * geomSmith(mat, NDotV);
vec3 F = schlickFresnelRoughness(mat, NDotV);
vec3 specBrdf = F * ggxDistribution(mat, NDotH) * geomSmith(mat, NDotV) * geomSmith(mat, NDotL);
specBrdf /= 4.0 * NDotL * NDotV + 0.0001;
return (diffuseBrdf + PI * specBrdf) * lightI * NDotL * shadow_mult + mat.emission;
vec3 kS = F;
vec3 kD = vec3(1.0) - kS;
kD *= 1.0 - mat.metallic;
return (kD * diffuseBrdf + specBrdf) * lightI * NDotL * shadow_mult;
}
vec3 ibl(EvalMaterial mat, vec3 N, vec3 V) {
float NDotV = max(dot(N, V), 0.0);
vec3 F = schlickFresnelRoughness(mat, NDotV);
vec3 kS = F;
vec3 kD = 1.0 - kS;
vec3 R = reflect(-V, N);
float ambient_diff = dot(N, VertexOut.vUp) * 0.5 + 0.5;
float ambient_spec = dot(R, VertexOut.vUp) * 0.5 + 0.5;
vec3 irradiance = vec3(1.0, 0.9764705882352941, 0.9921568627450981) * 79 * ambient_diff;
vec3 diffuse = irradiance * mat.albedo.rgb;
vec3 reflectedColor = vec3(0.9, 0.9064705882352941, 0.9921568627450981) * 79 * ambient_spec;
vec2 envBRDF = textureLod(brdfLut, vec2(NDotV, mat.roughness), 0).rg;
vec3 specular = reflectedColor * (F * envBRDF.x + envBRDF.y);
return kD * diffuse + specular;
}
void main() {
@ -311,13 +346,13 @@ void main() {
sampler2D normal_map = materials[materialIdx].normal_map;
vec2 normal_map_uv_scale = materials[materialIdx].normal_map_uv_scale;
EvalMaterial material = evalMaterial();
material.roughness = material.albedo.a < 1.0 ? 1.0 : material.roughness;
vec3 N = textureSize(normal_map, 0) == ivec2(0) ? vec3(0.5) : vec3(texture(normal_map, VertexOut.uv * normal_map_uv_scale).xy, 0);
N = N * 2.0 - 1.0;
N.z = sqrt(clamp(1 - N.x * N.x - N.y * N.y, 0, 1));
N = normalize(N);
N = normalize(VertexOut.vTBN * N);
// vec3 N = VertexOut.vNormal;
vec3 finalColor = vec3(0);
@ -327,10 +362,10 @@ void main() {
finalColor += microfacetModel(material, i, VertexOut.vPos, N);
}
float ambient_a = dot(VertexOut.wNormal, vec3(0, 1, 0)) * 0.5 + 0.5;
vec3 V = normalize(-VertexOut.vPos);
// ambient
finalColor += material.albedo.rgb * mix(vec3(0.0116, 0.0127, 0.0200), vec3(0.185, 0.198, 0.250), ambient_a);
finalColor += ibl(material, N, V);
finalColor += material.emission;
FragColor = vec4(finalColor, material.albedo.a);
}

View File

@ -69,7 +69,7 @@ vec3 linearToSRGB(vec3 color) {
void main() {
vec3 hdr_color = texture(screen_sampler, VertexOut.uv).rgb;
hdr_color = ACESFitted(hdr_color);
hdr_color = ACESFitted(hdr_color * 0.008);
FragColor.rgb = linearToSRGB(hdr_color);
FragColor.a = 1;

View File

@ -23,12 +23,12 @@ pub const MAX_DRAW_COMMANDS = 1024 * 16;
pub const MAX_LIGHT_COMMANDS = 2048;
pub const MAX_MATERIALS = MAX_DRAW_COMMANDS;
pub const CSM_SPLITS = 4;
pub const DIRECTIONAL_SHADOW_MAP_SIZE = 2048;
pub const DIRECTIONAL_SHADOW_MAP_SIZE = 4096;
// affects how cascades are split
// 0 - uniform
// 1 - exponential
// 0.5 - mix between the two
pub const CSM_EXPO_UNIFORM_FACTOR = 0.5;
pub const CSM_EXPO_UNIFORM_FACTOR = 0.8;
pub const Render = @This();
@ -614,6 +614,7 @@ pub fn finish(self: *Render) void {
light.view_mat = view;
light.params.shadow_map_idx = shadow_map_idx;
light.params.csm_split_count = @floatFromInt(CSM_SPLITS);
const shadow_far = self.camera.far / 2;
var splits: [CSM_SPLITS + 1]f32 = undefined;
@ -621,8 +622,8 @@ pub fn finish(self: *Render) void {
for (0..CSM_SPLITS + 1) |split_idx| {
const split_idx_f: f32 = @floatFromInt(split_idx);
const split_i_over_n = split_idx_f / splits_count_f;
const expo_split = self.camera.near * std.math.pow(f32, self.camera.far / self.camera.near, split_i_over_n);
const uniform_split = self.camera.near + split_i_over_n * (self.camera.far - self.camera.near);
const expo_split = self.camera.near * std.math.pow(f32, shadow_far / self.camera.near, split_i_over_n);
const uniform_split = self.camera.near + split_i_over_n * (shadow_far - self.camera.near);
const split = CSM_EXPO_UNIFORM_FACTOR * expo_split + (1.0 - CSM_EXPO_UNIFORM_FACTOR) * uniform_split;
splits[split_idx] = split;
}
@ -917,7 +918,7 @@ pub fn finish(self: *Render) void {
gl.bindVertexArray(self.mesh_vao);
gl.depthFunc(gl.EQUAL);
gl.uniform1ui(Uniform.LightsCount.value(), lights_buf.count.*);
gl.GL_ARB_bindless_texture.uniformHandleui64ARB(Uniform.EnvBRDF.value(), self.assetman.resolveTexture(a.Textures.@"ibl_brdf_lut.norm").handle);
gl.GL_ARB_bindless_texture.uniformHandleui64ARB(Uniform.ShadowMap2D.value(), self.shadow_texture_handle);
gl.GL_ARB_bindless_texture.uniformHandleui64ARB(Uniform.ShadowMapCube.value(), self.cube_shadow_texture_handle);
@ -1304,6 +1305,7 @@ pub const Uniform = enum(gl.GLint) {
SRCMipLevel = 19,
BloomStrength = 20,
LightsCount = 21,
EnvBRDF = 22,
pub inline fn value(self: Uniform) gl.GLint {
return @intFromEnum(self);

View File

@ -191,7 +191,7 @@ export fn game_init(global_allocator: *std.mem.Allocator) void {
_ = globals.g_mem.world.addEntity(.{
.flags = .{ .dir_light = true, .rotate = true },
.transform = .{ .rot = Quat.fromEulerAngles(Vec3.new(70, 0, 0)) },
.light = .{ .color_intensity = Vec4.new(1.00, 0.885, 0.570, 1.0) },
.light = .{ .color_intensity = Vec4.new(1.00, 0.805, 0.570, 790) },
.rotate = .{ .axis = Vec3.up(), .rate = -10 },
});
@ -204,7 +204,7 @@ export fn game_init(global_allocator: *std.mem.Allocator) void {
// const light1 = globals.g_mem.world.addEntity(.{
// .transform = .{ .pos = Vec3.new(1.8, 1, 0) },
// .flags = .{ .point_light = true, .rotate = true },
// .light = .{ .color_intensity = Vec4.new(1.0, 0.3, 0.1, 100.0) },
// .light = .{ .color_intensity = Vec4.new(1.0, 0.3, 0.1, 800.0 * 2) },
// .point_light = .{ .radius = 0.1 },
// .rotate = .{ .axis = Vec3.up(), .rate = -40 },
// });
@ -213,7 +213,7 @@ export fn game_init(global_allocator: *std.mem.Allocator) void {
// const light2 = globals.g_mem.world.addEntity(.{
// .transform = .{ .pos = Vec3.new(-2, 0, 0) },
// .flags = .{ .point_light = true, .rotate = true },
// .light = .{ .color_intensity = Vec4.new(0.2, 0.5, 1.0, 100.0) },
// .light = .{ .color_intensity = Vec4.new(0.2, 0.5, 1.0, 800.0 * 2) },
// .point_light = .{ .radius = 0.1 },
// });
// light2.ptr.setParent(light1.handle);
@ -221,7 +221,7 @@ export fn game_init(global_allocator: *std.mem.Allocator) void {
// _ = globals.g_mem.world.addEntity(.{
// .transform = .{ .pos = Vec3.new(1, 0.5, 4) },
// .flags = .{ .point_light = true },
// .light = .{ .color_intensity = Vec4.new(0.2, 0.5, 1.0, 10.0) },
// .light = .{ .color_intensity = Vec4.new(0.2, 0.5, 1.0, 800.0 * 2) },
// .point_light = .{ .radius = 1 },
// });
@ -356,7 +356,7 @@ export fn game_update() bool {
c.SDL_SCANCODE_F7 => {
if (event.type == c.SDL_KEYDOWN) {
if (gmem.render.camera.far == 10) {
gmem.render.camera.far = 50;
gmem.render.camera.far = 1000;
} else {
gmem.render.camera.far = 10;
}