diff --git a/assets/ibl_brdf_lut.norm.png b/assets/ibl_brdf_lut.norm.png new file mode 100644 index 0000000..35b1f4b --- /dev/null +++ b/assets/ibl_brdf_lut.norm.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:98fc63eada3ca5549bd74d518fd3e2fd5f84abcbe54366771f9c0224fa2c19f1 +size 35823 diff --git a/assets/shaders/mesh.glsl b/assets/shaders/mesh.glsl index 123c031..f94afcf 100644 --- a/assets/shaders/mesh.glsl +++ b/assets/shaders/mesh.glsl @@ -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 @@ -290,20 +300,45 @@ vec3 microfacetModel(EvalMaterial mat, int light_idx, vec3 P, vec3 N) { texcoord.xyw = shadow_pos.xyz; // sampler2DArrayShadow strange texcoord mapping texcoord.z = shadow_map_idx + csm_split_idx; - float sum = 0; - for (float y = -1.5; y <= 1.5; y += 1) { - for (float x = -1.5; x <= 1.5; x += 1) { - sum += texture(shadow_maps, vec4(texcoord.xy + vec2(x, y) * shadow_map_texel_size, texcoord.zw)); - } - } - + float sum = 0; + for (float y = -1.5; y <= 1.5; y += 1) { + for (float x = -1.5; x <= 1.5; x += 1) { + sum += texture(shadow_maps, vec4(texcoord.xy + vec2(x, y) * shadow_map_texel_size, texcoord.zw)); + } + } + shadow_mult = sum / 16.0; } 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; + + vec3 kS = F; + vec3 kD = vec3(1.0) - kS; + kD *= 1.0 - mat.metallic; - return (diffuseBrdf + PI * specBrdf) * lightI * NDotL * shadow_mult + mat.emission; + 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); } diff --git a/assets/shaders/post_process.glsl b/assets/shaders/post_process.glsl index bdacbe4..833344b 100644 --- a/assets/shaders/post_process.glsl +++ b/assets/shaders/post_process.glsl @@ -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; diff --git a/src/Render.zig b/src/Render.zig index e0f8b7c..4dc9880 100644 --- a/src/Render.zig +++ b/src/Render.zig @@ -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); diff --git a/src/game.zig b/src/game.zig index 844a394..348e56e 100644 --- a/src/game.zig +++ b/src/game.zig @@ -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; }