Skip to content

Instantly share code, notes, and snippets.

@michaeldll
Created March 5, 2024 13:26
Show Gist options
  • Save michaeldll/ea976e723967df998ca346c6ff9e053b to your computer and use it in GitHub Desktop.
Save michaeldll/ea976e723967df998ca346c6ff9e053b to your computer and use it in GitHub Desktop.
Compress KTX2 with toktx using Node.js
import { exec } from 'child_process';
import fs from 'fs';
import path from 'path';
// By Michael de Laborde
// This script compresses PNG and JPG textures to KTX2 using the Khronos toktx tool.
// Uses low quality but highly compressed ETC1S compression by default.
// REQUIREMENTS:
// Install toktx 4.3.1 from https://github.com/KhronosGroup/KTX-Software/releases/tag/v4.3.1
// USAGE:
// node compress-textures.js [options] input.jpg
// OPTIONS:
// --high - Use high quality UASTC compression, e.g. for normal maps
// --medium - Use UASTC compression
// --flip - Flip the texture vertically
// --noMipmaps - Do not generate mipmaps
// --linear - Use linear color space, e.g. for normal maps and other non-color textures such as ORM maps
// EXAMPLE USAGE FOR TEXTURES:
// Albedo: --low (=etc1s)
// Emissive: --low (=etc1s)
// Normal: --high --linear
// ORM: --high --linear OR --medium --linear
// Transmission: --medium --linear
// Clearcoat: --medium --linear
// Additional information: https://github.com/KhronosGroup/3D-Formats-Guidelines/blob/main/KTXArtistGuide.md
const args = process.argv.slice(2);
// Find the input file as the last argument
let input = args[args.length - 1];
// Check if input file exists
if (!fs.existsSync(input)) {
throw new Error('Input file does not exist');
}
// Find compression, use ETC1S by default
let compression = 'etc1s';
for (let i = 0; i < args.length; i++) {
if (args[i] === '--medium') {
compression = 'uastc';
} else if (args[i] === '--high') {
compression = 'uastcHighQuality';
}
}
// Check if --flip flag is present
let flip = '';
for (let i = 0; i < args.length; i++) {
if (args[i] === '--flip') {
flip = '--lower_left_maps_to_s0t0';
}
}
// Build mipmaps by default unless --noMipmaps flag is present
let mipmaps = '--genmipmap';
for (let i = 0; i < args.length; i++) {
if (args[i] === '--noMipmaps') {
mipmaps = '';
}
}
// Linear color space, e.g. for normal maps
let linear = '';
for (let i = 0; i < args.length; i++) {
if (args[i] === '--linear') {
linear = '--assign_oetf linear --assign_primaries none';
}
}
// Set compression command
let command = '';
if (compression === 'etc1s') {
command = '--t2 --encode etc1s --clevel 4 --qlevel 255';
} else if (compression === 'uastc') {
command = '--t2 --encode uastc --uastc_quality 4 --uastc_rdo_l .5 --uastc_rdo_d 65536 --zcmp 22';
} else if (compression === 'uastcHighQuality') {
command = '--t2 --encode uastc --uastc_quality 4 --uastc_rdo_l .2 --uastc_rdo_d 65536 --zcmp 22';
}
// Add new target directory
const parent = path.dirname(input);
const targetDirectory = parent + '/ktx2';
if (!fs.existsSync(targetDirectory)) {
fs.mkdirSync(targetDirectory);
}
// Set output extensions and path to new targetDirectory
const output = targetDirectory + '/' + path.basename(input.replace(/\.[^/.]+$/, '.ktx2'));
exec(`toktx ${command} ${flip} ${mipmaps} ${output} ${input}`, (error, stdout, stderr) => {
if (error) {
console.log(`error when building KTX2: ${error.message}`);
return;
}
if (stderr) {
console.log(`stderr when building KTX2: ${stderr}`);
return;
}
console.log('Successfully built KTX2 texture');
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment