Working normal maps!

This commit is contained in:
sergeypdev 2024-02-19 08:05:00 +04:00
parent 6e75910bef
commit 8f40efc6a2
6 changed files with 192 additions and 55 deletions

View File

@ -22,21 +22,21 @@ layout(std140, binding = 1) uniform Lights {
// Uniforms
layout(location = 1) uniform mat4 model;
layout(location = 2) uniform vec3 color;
layout(location = 3, bindless_sampler) uniform sampler2D diffuse;
layout(location = 3, bindless_sampler) uniform sampler2D albedo_map;
layout(location = 4, bindless_sampler) uniform sampler2D normal_map;
// Input, output blocks
VERTEX_EXPORT VertexData {
vec3 position;
vec3 normal;
vec2 uv;
vec3 tangent;
mat3 TBN;
} VertexOut;
#if VERTEX_SHADER
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNorm;
layout(location = 1) in vec3 aNormal;
layout(location = 2) in vec2 aUV;
layout(location = 3) in vec3 aTangent;
@ -44,9 +44,12 @@ void main() {
gl_Position = projection * view * model * vec4(aPos.xyz, 1.0);
vec4 posWorld = model * vec4(aPos, 1.0);
VertexOut.position = posWorld.xyz / posWorld.w;
VertexOut.normal = aNorm;
VertexOut.uv = aUV;
VertexOut.tangent = aTangent;
vec3 aBitangent = cross(aTangent, aNormal);
vec3 T = normalize(vec3(model * vec4(aTangent, 0.0)));
vec3 B = normalize(vec3(model * vec4(aBitangent, 0.0)));
vec3 N = normalize(vec3(model * vec4(aNormal, 0.0)));
VertexOut.TBN = mat3(T, B, N);
}
#endif // VERTEX_SHADER
@ -55,7 +58,14 @@ void main() {
out vec4 FragColor;
void main() {
vec3 diffuseColor = textureSize(diffuse, 0) == ivec2(0) ? color : texture(diffuse, VertexOut.uv).rgb;
vec3 albedoColor = textureSize(albedo_map, 0) == ivec2(0) ? color : texture(albedo_map, VertexOut.uv).rgb;
vec3 N = textureSize(normal_map, 0) == ivec2(0) ? vec3(0.5) : vec3(texture(normal_map, VertexOut.uv).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.TBN * N);
vec3 finalColor = vec3(0);
for (int i = 0; i < lights_count; i++) {
@ -70,9 +80,9 @@ void main() {
// TODO: cutoff
att = max(att, 0);
float ndotl = max(dot(L, VertexOut.normal), 0);
float ndotl = max(dot(L, N), 0);
finalColor += ndotl * lights[i].color.w * lights[i].color.xyz * att * diffuseColor;
finalColor += ndotl * lights[i].color.w * lights[i].color.xyz * att * albedoColor;
}
FragColor = vec4(finalColor, 1.0f);

BIN
assets/tile.norm.jpg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -375,10 +375,16 @@ fn loadTextureErr(self: *AssetManager, id: AssetId) !*const LoadedTexture {
}
errdefer gl.deleteTextures(1, &name);
const gl_format: gl.GLenum = switch (texture.header.format) {
.bc7 => gl.COMPRESSED_RGBA_BPTC_UNORM,
.bc5 => gl.COMPRESSED_RG_RGTC2,
.bc6 => gl.COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT,
};
gl.textureStorage2D(
name,
@intCast(texture.mipLevels()),
gl.COMPRESSED_RGBA_BPTC_UNORM,
gl_format,
@intCast(texture.header.width),
@intCast(texture.header.height),
);
@ -393,7 +399,7 @@ fn loadTextureErr(self: *AssetManager, id: AssetId) !*const LoadedTexture {
0,
@intCast(desc.width),
@intCast(desc.height),
gl.COMPRESSED_RGBA_BPTC_UNORM,
gl_format,
@intCast(texture.data[mip_level].len),
@ptrCast(texture.data[mip_level].ptr),
);

View File

@ -178,12 +178,16 @@ pub fn getPointLights(self: *Render) *PointLightArray {
}
pub fn draw(self: *Render, cmd: DrawCommand) void {
gl.uniformMatrix4fv(1, 1, gl.FALSE, @ptrCast(&cmd.transform.data));
gl.uniform3fv(2, 1, @ptrCast(&cmd.material.albedo.data));
gl.uniformMatrix4fv(Uniform.ModelMatrix.value(), 1, gl.FALSE, @ptrCast(&cmd.transform.data));
gl.uniform3fv(Uniform.Color.value(), 1, @ptrCast(&cmd.material.albedo.data));
gl.GL_ARB_bindless_texture.uniformHandleui64ARB(
3,
Uniform.AlbedoMap.value(),
self.assetman.resolveTexture(cmd.material.albedo_map).handle,
);
gl.GL_ARB_bindless_texture.uniformHandleui64ARB(
Uniform.NormalMap.value(),
self.assetman.resolveTexture(cmd.material.normal_map).handle,
);
const mesh = self.assetman.resolveMesh(cmd.mesh);
mesh.positions.bind(Render.Attrib.Position.value());
@ -256,6 +260,17 @@ pub const UBO = enum(gl.GLuint) {
}
};
pub const Uniform = enum(gl.GLint) {
ModelMatrix = 1,
Color = 2,
AlbedoMap = 3,
NormalMap = 4,
pub inline fn value(self: Uniform) gl.GLint {
return @intFromEnum(self);
}
};
// TODO: support ortho
pub const Camera = struct {
fovy: f32 = 60,
@ -288,4 +303,5 @@ pub const PointLightArray = extern struct {
pub const Material = struct {
albedo: Vec3 = Vec3.one(),
albedo_map: AssetManager.Handle.Texture = .{},
normal_map: AssetManager.Handle.Texture = .{},
};

View File

@ -162,8 +162,13 @@ export fn game_init(global_allocator: *std.mem.Allocator) void {
// Plane
_ = globals.g_mem.world.addEntity(.{
.flags = .{ .mesh = true },
.transform = .{ .scale = Vec3.one().scale(100) },
.mesh = .{ .handle = a.Meshes.plane },
.transform = .{ .scale = Vec3.one().scale(1) },
.mesh = .{
.handle = a.Meshes.plane,
.material = .{
.normal_map = a.Textures.@"tile.norm",
},
},
});
// 10 bunnies
@ -173,7 +178,13 @@ export fn game_init(global_allocator: *std.mem.Allocator) void {
.transform = .{ .pos = Vec3.new(@as(f32, @floatFromInt(i)) * 0.3, 0, 0) },
.flags = .{ .mesh = true },
.mesh = .{ .handle = a.Meshes.bunny, .material = .{ .albedo_map = a.Textures.bunny_tex1 } },
.mesh = .{
.handle = a.Meshes.bunny,
.material = .{
.albedo_map = a.Textures.bunny_tex1,
// .normal_map = a.Textures.@"tile.norm",
},
},
});
}
}

View File

@ -209,22 +209,29 @@ const MipLevel = struct {
fn processTexture(allocator: std.mem.Allocator, input: [*:0]const u8, output: []const u8, hdr: bool) !void {
_ = hdr; // autofix
// For tex.norm.png - this will be ".norm"
const sub_ext = std.fs.path.extension(std.fs.path.stem(std.mem.span(input)));
const format = if (std.mem.eql(u8, sub_ext, ".norm")) formats.Texture.Format.bc5 else formats.Texture.Format.bc7;
var width_int: c_int = undefined;
var height_int: c_int = undefined;
var comps: c_int = undefined;
c.stbi_set_flip_vertically_on_load(1);
const FORCED_COMPONENTS = 4; // force rgb
const data_c = c.stbi_load(input, &width_int, &height_int, &comps, FORCED_COMPONENTS);
if (data_c == null) {
const rgba_data_c = c.stbi_load(input, &width_int, &height_int, &comps, 4);
if (rgba_data_c == null) {
return error.ImageLoadError;
}
defer c.stbi_image_free(data_c);
defer c.stbi_image_free(rgba_data_c);
const width: usize = @intCast(width_int);
const height: usize = @intCast(height_int);
const data = data_c[0 .. width * height * FORCED_COMPONENTS];
const rgba_data = rgba_data_c[0 .. width * height * 4];
const data_channels: usize = if (format == .bc5) 2 else 4;
const data = if (data_channels < 4) dropChannels(rgba_data, data_channels) else rgba_data;
// TODO: support textures not divisible by 4
if (width % 4 != 0 or height % 4 != 0) {
@ -232,19 +239,11 @@ fn processTexture(allocator: std.mem.Allocator, input: [*:0]const u8, output: []
return error.ImageSizeShouldBeDivisibleBy4;
}
var settings: c.bc7_enc_settings = undefined;
if (comps == 3) {
c.GetProfile_ultrafast(&settings);
} else if (comps == 4) {
if (comps == 4) {
premultiplyAlpha(data);
c.GetProfile_alpha_ultrafast(&settings);
} else {
std.log.debug("Channel count: {}\n", .{comps});
return error.UnsupportedChannelCount;
}
const mip_levels_to_gen = 1 + @as(
const mip_levels_to_gen = if (data_channels == 2) 1 else 1 + @as(
u32,
@intFromFloat(@log2(@as(f32, @floatFromInt(@max(width, height))))),
);
@ -270,7 +269,7 @@ fn processTexture(allocator: std.mem.Allocator, input: [*:0]const u8, output: []
MipLevel{
.width = mip_width,
.height = mip_height,
.data = try allocator.alloc(u8, mip_width * mip_height * FORCED_COMPONENTS),
.data = try allocator.alloc(u8, mip_width * mip_height * data_channels),
},
);
actual_mip_count += 1;
@ -280,24 +279,14 @@ fn processTexture(allocator: std.mem.Allocator, input: [*:0]const u8, output: []
for (0..actual_mip_count) |mip_level| {
const mip_data = &mip_pyramid.items[mip_level];
if (mip_level > 0) {
downsampleImage2X(&mip_pyramid.items[mip_level - 1], mip_data);
switch (data_channels) {
2 => downsampleRGImage2X(&mip_pyramid.items[mip_level - 1], mip_data),
4 => downsampleRGBAImage2X(&mip_pyramid.items[mip_level - 1], mip_data),
else => unreachable,
}
}
const blocks_x: usize = mip_data.width / 4;
const blocks_y: usize = mip_data.height / 4;
const out_data = try allocator.alloc(u8, blocks_x * blocks_y * 16);
const rgba_surf = c.rgba_surface{
.width = @intCast(mip_data.width),
.height = @intCast(mip_data.height),
.stride = @intCast(mip_data.width * FORCED_COMPONENTS),
.ptr = mip_data.data.ptr,
};
c.CompressBlocksBC7(&rgba_surf, out_data.ptr, &settings);
mip_data.out_data = out_data;
mip_data.out_data = try compressBlocksAlloc(allocator, mip_data.data, data_channels, format, @intCast(comps), mip_data.width, mip_data.height);
}
const out_data = try allocator.alloc([]const u8, actual_mip_count);
@ -307,7 +296,7 @@ fn processTexture(allocator: std.mem.Allocator, input: [*:0]const u8, output: []
const texture = formats.Texture{
.header = .{
.format = .bc7,
.format = format,
.width = @intCast(width),
.height = @intCast(height),
.mip_count = @intCast(actual_mip_count),
@ -322,6 +311,64 @@ fn processTexture(allocator: std.mem.Allocator, input: [*:0]const u8, output: []
try buf_writer.flush();
}
fn compressBlocksAlloc(
allocator: std.mem.Allocator,
pixels: []u8,
components: usize, // 2 for normal maps, 4 for everything else
format: formats.Texture.Format,
original_components: usize, // how many components in original image. Does not match actual components
width: usize,
height: usize,
) ![]u8 {
std.debug.assert(width % 4 == 0);
std.debug.assert(height % 4 == 0);
const blocks_x = width / 4;
const blocks_y = height / 4;
const rgba_surf = c.rgba_surface{
.width = @intCast(width),
.height = @intCast(height),
.stride = @intCast(width * components),
.ptr = pixels.ptr,
};
const output = try allocator.alloc(u8, blocks_x * blocks_y * 16);
switch (format) {
.bc7 => {
var settings: c.bc7_enc_settings = .{};
if (original_components == 3) {
c.GetProfile_ultrafast(&settings);
} else if (original_components == 4) {
c.GetProfile_alpha_ultrafast(&settings);
} else {
std.log.debug("Channel count: {}\n", .{original_components});
return error.UnsupportedChannelCount;
}
c.CompressBlocksBC7(&rgba_surf, output.ptr, &settings);
},
.bc5 => {
std.debug.assert(components == 2);
c.CompressBlocksBC5(&rgba_surf, output.ptr);
},
.bc6 => {
return error.NotImplementedYet;
},
}
return output;
}
fn dropChannels(rgba_data: []u8, channel_count: usize) []u8 {
for (0..rgba_data.len / 4) |i| {
for (0..channel_count) |j| {
rgba_data[i * 2 + j] = rgba_data[i * 4 + j];
}
}
return rgba_data[0 .. (rgba_data.len / 4) * channel_count];
}
const gamma = 2.2;
const srgb_to_linear: [256]u8 = blk: {
@setEvalBranchQuota(10000);
@ -365,7 +412,31 @@ inline fn vecPow(x: @Vector(4, f32), y: f32) @Vector(4, f32) {
return @exp(@log(x) * @as(@Vector(4, f32), @splat(y)));
}
fn downsampleImage2X(src: *const MipLevel, dst: *const MipLevel) void {
fn downsampleRGImage2X(src: *const MipLevel, dst: *const MipLevel) void {
const srcStride = src.width * 2;
const dstStride = dst.width * 2;
for (0..dst.height) |y| {
for (0..dst.width) |x| {
const x0 = x * 2;
const y0 = y * 2;
var result = @Vector(2, f32){ 0, 0 };
for (0..2) |y1| {
for (0..2) |x1| {
const srcX = x0 + x1;
const srcY = y0 + y1;
result += loadColorVec2(src.data[srcY * srcStride + srcX * 2 ..]);
}
}
result /= @splat(2);
storeColorVec2(dst.data[y * dstStride + x * 2 ..], result);
}
}
}
fn downsampleRGBAImage2X(src: *const MipLevel, dst: *const MipLevel) void {
const srcStride = src.width * 4;
const dstStride = dst.width * 4;
for (0..dst.height) |y| {
@ -379,17 +450,37 @@ fn downsampleImage2X(src: *const MipLevel, dst: *const MipLevel) void {
const srcX = x0 + x1;
const srcY = y0 + y1;
result += loadColorVec(src.data[srcY * srcStride + srcX * 4 ..]);
result += loadColorVec4(src.data[srcY * srcStride + srcX * 4 ..]);
}
}
result /= @splat(4);
storeColorVec(dst.data[y * dstStride + x * 4 ..], result);
storeColorVec4(dst.data[y * dstStride + x * 4 ..], result);
}
}
}
inline fn loadColorVec(pixel: []const u8) @Vector(4, f32) {
inline fn loadColorVec2(pixel: []const u8) @Vector(2, f32) {
@setRuntimeSafety(false);
std.debug.assert(pixel.len >= 2);
return @Vector(2, f32){
@as(f32, @floatFromInt(pixel[0])),
@as(f32, @floatFromInt(pixel[1])),
} / @as(@Vector(2, f32), @splat(255.0));
}
inline fn storeColorVec2(pixel: []u8, vec: @Vector(2, f32)) void {
@setRuntimeSafety(false);
std.debug.assert(pixel.len >= 2);
const out = vec * @as(@Vector(2, f32), @splat(255.0));
pixel[0] = @intFromFloat(out[0]);
pixel[1] = @intFromFloat(out[1]);
}
inline fn loadColorVec4(pixel: []const u8) @Vector(4, f32) {
@setRuntimeSafety(false);
std.debug.assert(pixel.len >= 4);
@ -401,7 +492,7 @@ inline fn loadColorVec(pixel: []const u8) @Vector(4, f32) {
} / @as(@Vector(4, f32), @splat(255.0));
}
inline fn storeColorVec(pixel: []u8, vec: @Vector(4, f32)) void {
inline fn storeColorVec4(pixel: []u8, vec: @Vector(4, f32)) void {
@setRuntimeSafety(false);
std.debug.assert(pixel.len >= 4);