diff --git a/assets/shaders/mesh.glsl b/assets/shaders/mesh.glsl index f94afcf..34cb3bf 100644 --- a/assets/shaders/mesh.glsl +++ b/assets/shaders/mesh.glsl @@ -55,8 +55,8 @@ layout(location = 2) in vec2 aUV; layout(location = 3) in vec3 aTangent; void main() { - DrawID = gl_DrawID; - mat4 model = draw_data[gl_DrawID].transform; + DrawID = gl_BaseInstance + gl_InstanceID; + mat4 model = draw_data[DrawID].transform; mat4 viewModel = view * model; vec4 vPos = viewModel * vec4(aPos.xyz, 1.0); gl_Position = projection * vPos; @@ -126,13 +126,6 @@ layout(std430, binding = 2) readonly buffer Materials { out vec4 FragColor; -struct EvalMaterial { - vec4 albedo; - float metallic; - float roughness; - vec3 emission; -}; - int getShadowMapIndex(int lightIdx) { return int(lights[lightIdx].params.z); } @@ -153,39 +146,44 @@ int getCSMSplit(int lightIdx, float depth) { return CSM_SPLITS - 1; } -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); - 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; +vec4 getAlbedo(int materialIdx) { + return 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); } -vec3 schlickFresnel(EvalMaterial mat, float NDotV) { - vec3 f0 = mix(vec3(0.04), mat.albedo.rgb, mat.metallic); +float getRoughness(int materialIdx) { + return 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); +} + +float getMetallic(int materialIdx) { + return 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; +} + +vec3 getEmission(int materialIdx) { + return 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; +} + +vec3 schlickFresnel(int matIdx, float NDotV) { + vec3 f0 = mix(vec3(0.04), getAlbedo(matIdx).rgb, getMetallic(matIdx)); 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); +vec3 schlickFresnelRoughness(int matIdx, float NDotV) { + vec3 f0 = mix(vec3(0.04), getAlbedo(matIdx).rgb, getMetallic(matIdx)); - return f0 + (max(vec3(1.0 - mat.roughness), f0) - f0) * pow(1.0 - NDotV, 5.0); + return f0 + (max(vec3(1.0 - getRoughness(matIdx)), 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 geomSmith(int matIdx, float DotVal) { + float k = ((getRoughness(matIdx) + 1.0) * (getRoughness(matIdx) + 1.0)) / 8.0; float denom = DotVal * (1 - k) + k; return DotVal / denom; } -float ggxDistribution(EvalMaterial mat, float NDotH) { - float a = mat.roughness * mat.roughness; +float ggxDistribution(int matIdx, float NDotH) { + float a = getRoughness(matIdx) * getRoughness(matIdx); float alpha2 = a * a; float NDotH2 = NDotH * NDotH; float nom = alpha2; @@ -223,12 +221,12 @@ float map(float value, float min1, float max1, float min2, float max2) { return min2 + (value - min1) * (max2 - min2) / (max1 - min1); } -vec3 microfacetModel(EvalMaterial mat, int light_idx, vec3 P, vec3 N) { +vec3 microfacetModel(int matIdx, int light_idx, vec3 P, vec3 N) { // Light light = lights[light_idx]; vec3 diffuseBrdf = vec3(0); // metallic - if (mat.metallic < 1.0) { - diffuseBrdf = mat.albedo.rgb / PI; + if (getMetallic(matIdx) < 1.0) { + diffuseBrdf = getAlbedo(matIdx).rgb / PI; } // 0 - means directional, 1 - means point light @@ -311,20 +309,20 @@ vec3 microfacetModel(EvalMaterial mat, int light_idx, vec3 P, vec3 N) { } shadow_mult = clamp(shadow_mult, 0.0, 1.0); - vec3 F = schlickFresnelRoughness(mat, NDotV); - vec3 specBrdf = F * ggxDistribution(mat, NDotH) * geomSmith(mat, NDotV) * geomSmith(mat, NDotL); + vec3 F = schlickFresnelRoughness(matIdx, NDotV); + vec3 specBrdf = F * (ggxDistribution(matIdx, NDotH) * geomSmith(matIdx, NDotV) * geomSmith(matIdx, NDotL)); specBrdf /= 4.0 * NDotL * NDotV + 0.0001; vec3 kS = F; vec3 kD = vec3(1.0) - kS; - kD *= 1.0 - mat.metallic; + kD *= 1.0 - getMetallic(matIdx); - return (kD * diffuseBrdf + specBrdf) * lightI * NDotL * shadow_mult; + return (kD * diffuseBrdf + specBrdf) * lightI * (NDotL * shadow_mult); } -vec3 ibl(EvalMaterial mat, vec3 N, vec3 V) { +vec3 ibl(int matIdx, vec3 N, vec3 V) { float NDotV = max(dot(N, V), 0.0); - vec3 F = schlickFresnelRoughness(mat, NDotV); + vec3 F = schlickFresnelRoughness(matIdx, NDotV); vec3 kS = F; vec3 kD = 1.0 - kS; @@ -333,19 +331,18 @@ vec3 ibl(EvalMaterial mat, vec3 N, vec3 V) { 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 diffuse = irradiance * getAlbedo(matIdx).rgb; vec3 reflectedColor = vec3(0.9, 0.9064705882352941, 0.9921568627450981) * 79 * ambient_spec; - vec2 envBRDF = textureLod(brdfLut, vec2(NDotV, mat.roughness), 0).rg; + vec2 envBRDF = textureLod(brdfLut, vec2(NDotV, getRoughness(matIdx)), 0).rg; vec3 specular = reflectedColor * (F * envBRDF.x + envBRDF.y); return kD * diffuse + specular; } void main() { - int materialIdx = draw_data[DrawID].materialIdx; - sampler2D normal_map = materials[materialIdx].normal_map; - vec2 normal_map_uv_scale = materials[materialIdx].normal_map_uv_scale; - EvalMaterial material = evalMaterial(); + int matIdx = draw_data[DrawID].materialIdx; + sampler2D normal_map = materials[matIdx].normal_map; + vec2 normal_map_uv_scale = materials[matIdx].normal_map_uv_scale; 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; @@ -356,18 +353,17 @@ void main() { vec3 finalColor = vec3(0); - // int n_lights = clamp(int(lights_count), 0, MAX_POINT_LIGHTS); - for (int i = 0; i < MAX_POINT_LIGHTS; i++) { - if (i >= lights_count) break; - finalColor += microfacetModel(material, i, VertexOut.vPos, N); + int n_lights = clamp(int(lights_count), 0, MAX_POINT_LIGHTS); + for (int i = 0; i < n_lights; i++) { + finalColor += microfacetModel(matIdx, i, VertexOut.vPos, N); } vec3 V = normalize(-VertexOut.vPos); // ambient - finalColor += ibl(material, N, V); - finalColor += material.emission; + finalColor += ibl(matIdx, N, V); + finalColor += getEmission(matIdx); - FragColor = vec4(finalColor, material.albedo.a); + FragColor = vec4(finalColor, getAlbedo(matIdx).a); } diff --git a/assets/shaders/post_process.glsl b/assets/shaders/post_process.glsl index 833344b..cc5b7da 100644 --- a/assets/shaders/post_process.glsl +++ b/assets/shaders/post_process.glsl @@ -69,9 +69,9 @@ vec3 linearToSRGB(vec3 color) { void main() { vec3 hdr_color = texture(screen_sampler, VertexOut.uv).rgb; - hdr_color = ACESFitted(hdr_color * 0.008); + hdr_color = ACESFitted(hdr_color * 0.02); - FragColor.rgb = linearToSRGB(hdr_color); + FragColor.rgb = hdr_color; FragColor.a = 1; } diff --git a/assets/shaders/z_prepass.glsl b/assets/shaders/z_prepass.glsl index 363923e..6649154 100644 --- a/assets/shaders/z_prepass.glsl +++ b/assets/shaders/z_prepass.glsl @@ -20,7 +20,7 @@ layout(std430, binding = 3) readonly buffer DrawCmdDatas { layout(location = 0) in vec3 aPos; void main() { - mat4 model = draw_data[gl_DrawID].transform; + mat4 model = draw_data[gl_BaseInstance + gl_InstanceID].transform; mat4 viewModel = view * model; vec4 vPos = viewModel * vec4(aPos.xyz, 1.0); gl_Position = projection * vPos; diff --git a/src/Render.zig b/src/Render.zig index 4dc9880..fcfe681 100644 --- a/src/Render.zig +++ b/src/Render.zig @@ -429,9 +429,9 @@ pub const LightCommand = union(LightKind) { }; const DrawCommandKey = packed struct { - transparent: u1 = 0, - distance: u15 = 0, mesh: u16 = 0, + distance: u15 = 0, + transparent: u1 = 0, }; pub fn drawLight(self: *Render, cmd: LightCommand) void { @@ -449,7 +449,7 @@ pub fn draw(self: *Render, cmd: DrawCommand) void { const dist: u15 = @intFromFloat(std.math.clamp(view_origin.distance(cmd.transform.extractTranslation()) / max_value, 0.0, max_value)); const key = DrawCommandKey{ .transparent = if (material.blend_mode == .AlphaBlend) 1 else 0, - .distance = if (material.blend_mode == .AlphaBlend) ~dist else dist, // TODO: calculate distance. Opaque should be front to back, transparent back to front + .distance = if (material.blend_mode == .AlphaBlend) dist else dist, // TODO: calculate distance. Opaque should be front to back, transparent back to front .mesh = @intCast(cmd.mesh.id % std.math.maxInt(u16)), }; self.command_buffer[self.command_count].key = key; @@ -547,6 +547,20 @@ pub fn finish(self: *Render) void { defer zoneSort.deinit(); self.sortCommands(self.command_buffer[0..self.command_count]); + + // Sorting validation + if (false) { + var alpha = false; + for (self.command_buffer[0..self.command_count]) |cmd| { + if (!alpha and cmd.key.transparent == 1) { + alpha = true; + } + + if (alpha and cmd.key.transparent == 0) { + std.log.err("WRONG SORTING!\n", .{}); + } + } + } } if (self.update_view_frustum) { @@ -801,8 +815,7 @@ pub fn finish(self: *Render) void { gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - const switched_to_alpha_blend = false; - gl.disable(gl.BLEND); + var switched_to_alpha_blend = false; var draw_indirect_cmds = self.frame_arena.alloc(DrawIndirectCmd, MAX_DRAW_COMMANDS) catch @panic("OOM"); var draw_cmd_data = self.frame_arena.alloc(DrawCommandData, MAX_DRAW_COMMANDS) catch @panic("OOM"); @@ -818,6 +831,7 @@ pub fn finish(self: *Render) void { defer gl.deleteBuffers(1, &draw_cmd_data_buf); var rendered_count: usize = 0; + var rendered_opaque_count: usize = 0; // Prepare indirect draw commands { @@ -830,7 +844,7 @@ pub fn finish(self: *Render) void { var material_map = std.StringHashMap(i32).init(self.frame_arena); var materials_count: usize = 0; - cmds: for (self.command_buffer[0..self.command_count]) |*cmd| { + for (self.command_buffer[0..self.command_count]) |*cmd| { const mesh = self.assetman.resolveMesh(cmd.mesh); // const aabb = math.AABB.fromMinMax(mesh.aabb.min, mesh.aabb.max); @@ -842,10 +856,9 @@ pub fn finish(self: *Render) void { // Opaque objects are drawn, start rendering alpha blended objects if (material.blend_mode == .AlphaBlend and !switched_to_alpha_blend) { - break :cmds; - // switched_to_alpha_blend = true; - // gl.enable(gl.BLEND); - // gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + rendered_opaque_count = rendered_count; + std.log.debug("opaque: {}\n", .{rendered_opaque_count}); + switched_to_alpha_blend = true; } const material_bytes = std.mem.asBytes(&material); @@ -868,13 +881,17 @@ pub fn finish(self: *Render) void { .instance_count = 1, .first_index = mesh.indices.offset / 4, .base_vertex = mesh.indices.base_vertex, - .base_instance = 0, + .base_instance = @intCast(rendered_count), }; rendered_count += 1; } } + if (rendered_opaque_count == 0) { + rendered_opaque_count = rendered_count; + } + { const camera_matrix: *CameraMatrices = @alignCast(@ptrCast(self.camera_matrices[self.tripple_buffer_index * self.uboAlignedSizeOf(CameraMatrices) ..].ptr)); camera_matrix.* = .{ @@ -903,13 +920,15 @@ pub fn finish(self: *Render) void { { gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.z_prepass).program); gl.bindVertexArray(self.shadow_vao); + gl.depthFunc(gl.LESS); self.assetman.vertex_heap.vertices.bind(Render.Attrib.Position.value()); checkGLError(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, self.assetman.vertex_heap.indices.buffer); checkGLError(); - gl.multiDrawElementsIndirect(gl.TRIANGLES, gl.UNSIGNED_INT, null, @intCast(rendered_count), @sizeOf(DrawIndirectCmd)); + gl.multiDrawElementsIndirect(gl.TRIANGLES, gl.UNSIGNED_INT, null, @intCast(rendered_opaque_count), @sizeOf(DrawIndirectCmd)); + checkGLError(); } // Main pass @@ -933,11 +952,25 @@ pub fn finish(self: *Render) void { gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, self.assetman.vertex_heap.indices.buffer); checkGLError(); - gl.multiDrawElementsIndirect(gl.TRIANGLES, gl.UNSIGNED_INT, null, @intCast(rendered_count), 0); + gl.multiDrawElementsIndirect(gl.TRIANGLES, gl.UNSIGNED_INT, null, @intCast(rendered_opaque_count), @sizeOf(DrawIndirectCmd)); + checkGLError(); } - gl.disable(gl.BLEND); - gl.depthFunc(gl.LESS); + // Alpha Pass + const blended_draws_count = rendered_count - rendered_opaque_count; + if (blended_draws_count > 0) { + std.log.debug("blended: {}\n", .{blended_draws_count}); + gl.enable(gl.BLEND); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + gl.depthFunc(gl.LEQUAL); + gl.depthMask(gl.FALSE); + + gl.multiDrawElementsIndirect(gl.TRIANGLES, gl.UNSIGNED_INT, @ptrFromInt(@sizeOf(DrawIndirectCmd) * rendered_opaque_count), @intCast(blended_draws_count), @sizeOf(DrawIndirectCmd)); + + gl.disable(gl.BLEND); + gl.depthFunc(gl.LEQUAL); + gl.depthMask(gl.TRUE); + } // Debug stuff { diff --git a/src/formats.zig b/src/formats.zig index 52ad392..88e5bc9 100644 --- a/src/formats.zig +++ b/src/formats.zig @@ -370,6 +370,7 @@ pub const Material = extern struct { pub const BlendMode = enum(u8) { Opaque = 0, AlphaBlend = 1, + AlphaMask = 2, }; albedo: Vec4 = Vec4.one(), diff --git a/src/game.zig b/src/game.zig index 88be45d..f866994 100644 --- a/src/game.zig +++ b/src/game.zig @@ -505,7 +505,7 @@ export fn game_update() bool { gmem.render.drawLight(.{ .directional = .{ .dir = dir4.toVec3(), - .color = color, + .color = color.add(Vec3.new(100, 0, 0)), }, }); } diff --git a/tools/asset_compiler.zig b/tools/asset_compiler.zig index 2b399c0..5f63461 100644 --- a/tools/asset_compiler.zig +++ b/tools/asset_compiler.zig @@ -193,6 +193,18 @@ fn processScene(allocator: std.mem.Allocator, input: []const u8, output_dir: std } } + for (texture_outputs) |*tex_out| { + defer tex_out.asset.file.close(); + + try processTexture( + allocator, + tex_out.tex_type, + @as([*]u8, @ptrCast(tex_out.texture.pcData))[0..@intCast(tex_out.texture.mWidth)], + tex_out.asset.file, + &tex_out.has_alpha, + ); + } + // Materials var material_outputs = try allocator.alloc(formats.Material, @intCast(scene.mNumMaterials)); if (scene.mMaterials != null) { @@ -205,31 +217,41 @@ fn processScene(allocator: std.mem.Allocator, input: []const u8, output_dir: std if (c.aiGetMaterialColor(material, AI_MATKEY_BASE_COLOR, 0, 0, &base_color) == c.aiReturn_SUCCESS) { // TODO: rgba mat_output.albedo = Vec4.new(base_color.r, base_color.g, base_color.b, base_color.a); + + if (std.math.approxEqAbs(f32, base_color.a, 0.0, std.math.floatEps(f32))) { + mat_output.blend_mode = .AlphaMask; + } else if (base_color.a < 1.0) { + mat_output.blend_mode = .AlphaBlend; + } } if (c.aiGetMaterialTextureCount(material, c.aiTextureType_BASE_COLOR) > 0) { const mat_texture = try getMaterialTexture(allocator, input_dir, material, c.aiTextureType_BASE_COLOR, 0); - const entry = mat_texture.path.resolveAssetListEntry(texture_outputs); + var has_alpha = false; + const entry = mat_texture.path.resolveAssetListEntry(texture_outputs, &has_alpha); mat_output.albedo_map.id = entry.getAssetId(); + if (has_alpha) { + mat_output.blend_mode = .AlphaBlend; + } } _ = c.aiGetMaterialFloat(material, AI_MATKEY_METALLIC_FACTOR, 0, 0, &mat_output.metallic); if (c.aiGetMaterialTextureCount(material, c.aiTextureType_METALNESS) > 0) { const mat_texture = try getMaterialTexture(allocator, input_dir, material, c.aiTextureType_METALNESS, 0); - const entry = mat_texture.path.resolveAssetListEntry(texture_outputs); + const entry = mat_texture.path.resolveAssetListEntry(texture_outputs, null); mat_output.metallic_map.id = entry.getAssetId(); } _ = c.aiGetMaterialFloat(material, AI_MATKEY_ROUGHNESS_FACTOR, 0, 0, &mat_output.roughness); if (c.aiGetMaterialTextureCount(material, c.aiTextureType_DIFFUSE_ROUGHNESS) > 0) { const mat_texture = try getMaterialTexture(allocator, input_dir, material, c.aiTextureType_DIFFUSE_ROUGHNESS, 0); - const entry = mat_texture.path.resolveAssetListEntry(texture_outputs); + const entry = mat_texture.path.resolveAssetListEntry(texture_outputs, null); mat_output.roughness_map.id = entry.getAssetId(); } if (c.aiGetMaterialTextureCount(material, c.aiTextureType_NORMALS) > 0) { const mat_texture = try getMaterialTexture(allocator, input_dir, material, c.aiTextureType_NORMALS, 0); - const entry = mat_texture.path.resolveAssetListEntry(texture_outputs); + const entry = mat_texture.path.resolveAssetListEntry(texture_outputs, null); switch (mat_texture.path) { .embedded => |idx| { texture_outputs[idx].tex_type = .Normal; @@ -242,12 +264,6 @@ fn processScene(allocator: std.mem.Allocator, input: []const u8, output_dir: std } } - for (texture_outputs) |tex_out| { - defer tex_out.asset.file.close(); - - try processTexture(allocator, tex_out.tex_type, @as([*]u8, @ptrCast(tex_out.texture.pcData))[0..@intCast(tex_out.texture.mWidth)], tex_out.asset.file); - } - const meshes: []*c.aiMesh = @ptrCast(scene.mMeshes[0..@intCast(scene.mNumMeshes)]); var mesh_outputs = try allocator.alloc(AssetListEntry, meshes.len); for (meshes, 0..) |mesh, i| { @@ -351,6 +367,7 @@ const TextureOutput = struct { texture: *c.aiTexture, asset: AssetOutput, tex_type: TextureType = .Color, + has_alpha: bool = false, }; const AssimpTextureRef = union(enum) { @@ -373,13 +390,17 @@ const AssimpTextureRef = union(enum) { return .{ .external = cwd_relative_path }; } - pub fn resolveAssetListEntry(self: AssimpTextureRef, embedded: []const TextureOutput) AssetListEntry { + pub fn resolveAssetListEntry(self: AssimpTextureRef, embedded: []const TextureOutput, out_has_alpha: ?*bool) AssetListEntry { switch (self) { .embedded => |idx| { + if (out_has_alpha) |has_alpha| { + has_alpha.* = embedded[idx].has_alpha; + } return embedded[idx].asset.list_entry; }, .external => |path| { // TODO: resolve relative to current input file + // TODO: has_alpha return AssetListEntry{ .src_path = AssetPath.fromString(path), .type = .Texture }; }, } @@ -547,7 +568,8 @@ fn processTextureFromFile(allocator: std.mem.Allocator, input: []const u8, outpu const contents = try std.fs.cwd().readFileAlloc(allocator, input, ASSET_MAX_BYTES); const texture_type = guessTextureTypeFromName(input); - try processTexture(allocator, texture_type, contents, output.file); + var has_alpha = false; + try processTexture(allocator, texture_type, contents, output.file, &has_alpha); } /// Using naming conventions @@ -576,7 +598,7 @@ const TextureType = enum { HDR, }; -fn processTexture(allocator: std.mem.Allocator, texture_type: TextureType, contents: []const u8, out_file: std.fs.File) !void { +fn processTexture(allocator: std.mem.Allocator, texture_type: TextureType, contents: []const u8, out_file: std.fs.File, out_has_alpha: *bool) !void { var width_int: c_int = 0; var height_int: c_int = 0; var comps: c_int = 0; @@ -601,6 +623,16 @@ fn processTexture(allocator: std.mem.Allocator, texture_type: TextureType, conte const rgba_data = rgba_data_c[0 .. width * height * 4]; + if (comps == 4) { + var i: usize = 3; + while (i < rgba_data.len) : (i += 4) { + if (rgba_data[i] < 255) { + out_has_alpha.* = true; + break; + } + } + } + var padded_width: usize = width; var padded_height: usize = height; var rgba_data_padded = rgba_data;