feat: pbr

This commit is contained in:
TrèsAbhi committed 2023-12-22 15:59:06 -06:00
1 parent 0627f678ea
commit d60a102c0d
6 files changed
+201 -90

No files matched your search

+1
View File
@@ -36,6 +36,7 @@
"krieg", "krieg",
"Leaderboard", "Leaderboard",
"leaderboards", "leaderboards",
"metallicness",
"MURICA", "MURICA",
"nmatname", "nmatname",
"noout", "noout",
+117 -40
View File
@@ -1,7 +1,7 @@
import { Document, Material, Node, NodeIO, Scene } from '@gltf-transform/core'; import { Document, Material, Node, NodeIO, Scene } from '@gltf-transform/core';
import { config } from 'dotenv'; import { config } from 'dotenv';
import { existsSync, writeFileSync } from 'fs'; import { writeFileSync } from 'fs';
import { mkdir, readdir, rm, writeFile } from 'fs/promises'; import { mkdir, readdir } from 'fs/promises';
import { range, times } from 'lodash'; import { range, times } from 'lodash';
import { dirname } from 'path'; import { dirname } from 'path';
import { argv } from 'process'; import { argv } from 'process';
@@ -23,6 +23,7 @@ import { toUniqueId } from '../src/core/blitz/toUniqueId';
import commitMultipleFiles, { import commitMultipleFiles, {
FileChange, FileChange,
} from '../src/core/blitzkrieg/commitMultipleFiles'; } from '../src/core/blitzkrieg/commitMultipleFiles';
import { readTexture } from '../src/core/blitzkrieg/readTexture';
import { import {
GunDefinition, GunDefinition,
ShellDefinition, ShellDefinition,
@@ -32,8 +33,7 @@ import {
Tier, Tier,
TurretDefinition, TurretDefinition,
} from '../src/core/blitzkrieg/tankDefinitions'; } from '../src/core/blitzkrieg/tankDefinitions';
import { toPng } from '../src/core/blitzkrieg/toPng'; import { Hierarchy, Textures } from '../src/types/sc2';
import { Hierarchy } from '../src/types/sc2';
const vertexAttributeGLTFName: Partial<Record<VertexAttribute, string>> = { const vertexAttributeGLTFName: Partial<Record<VertexAttribute, string>> = {
[VertexAttribute.VERTEX]: 'POSITION', [VertexAttribute.VERTEX]: 'POSITION',
@@ -507,12 +507,11 @@ if (allTargets || targets?.includes('circleFlags')) {
if (allTargets || targets?.includes('tankModels')) { if (allTargets || targets?.includes('tankModels')) {
console.log('Building tank models...'); console.log('Building tank models...');
if (existsSync('dist/assets/models')) { // if (existsSync('dist/assets/models')) {
await rm('dist/assets/models', { recursive: true }); // await rm('dist/assets/models', { recursive: true });
} // }
await mkdir('dist/assets/models', { recursive: true });
const changes: FileChange[] = []; const nodeIO = new NodeIO();
const nations = await readdir(`${DATA}/${DOI.vehicleDefinitions}`).then( const nations = await readdir(`${DATA}/${DOI.vehicleDefinitions}`).then(
(nations) => nations.filter((nation) => nation !== 'common'), (nations) => nations.filter((nation) => nation !== 'common'),
); );
@@ -528,8 +527,7 @@ if (allTargets || targets?.includes('tankModels')) {
const nationVehicleId = tank.id; const nationVehicleId = tank.id;
const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1; const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1;
// if (id !== 6225) continue; if (id !== 6753) continue;
if (id !== 20257) continue;
console.log(`Building model ${id} @ ${nation}/${tankIndex}`); console.log(`Building model ${id} @ ${nation}/${tankIndex}`);
const parameters = await readYAMLDVPL<TankParameters>( const parameters = await readYAMLDVPL<TankParameters>(
@@ -556,7 +554,7 @@ if (allTargets || targets?.includes('tankModels')) {
const materials = new Map<bigint, Material | bigint>(); const materials = new Map<bigint, Material | bigint>();
await Promise.all( await Promise.all(
sc2['#dataNodes'].map(async (node) => { sc2['#dataNodes'].map(async (node, index) => {
const id = node['#id'].readBigUInt64LE(); const id = node['#id'].readBigUInt64LE();
if (node.parentMaterialKey !== undefined) { if (node.parentMaterialKey !== undefined) {
@@ -565,38 +563,107 @@ if (allTargets || targets?.includes('tankModels')) {
} }
const material = document.createMaterial(node.materialName); const material = document.createMaterial(node.materialName);
const albedoTexture = document let textures: Textures | undefined = undefined;
.createTexture(node.materialName)
.setMimeType('image/png');
let albedoPathRaw: string | undefined = undefined;
if (node.textures) { if (node.textures) {
albedoPathRaw = node.textures.albedo; textures = node.textures;
} else if (typeof node.configCount === 'number') { } else if (typeof node.configCount === 'number') {
albedoPathRaw = node.configArchive_0.textures.albedo; textures = node.configArchive_0.textures;
} }
const albedoTexturePath = albedoPathRaw console.log(textures);
? `${DATA}/${DOI['3d']}/${dirname(
if (textures) {
const albedoJpg = await readTexture(
`${DATA}/${DOI['3d']}/${dirname(
parameters.resourcesPath.blitzModelPath, parameters.resourcesPath.blitzModelPath,
)}/${albedoPathRaw.replace('.tex', '.dx11.dds.dvpl')}` )}/${textures.albedo}`,
: undefined;
if (albedoTexturePath) {
const isDds = existsSync(albedoTexturePath);
const resolvedAlbedoTexturePath = isDds
? albedoTexturePath
: albedoTexturePath.replace('.dds', '.pvr');
const decompressedAlbedo = await readDVPLFile(
resolvedAlbedoTexturePath,
); );
const albedoPng = await toPng(decompressedAlbedo, { const albedoTexture = document
format: isDds ? 'dds' : 'pvr', .createTexture(node.materialName)
alpha: false, .setMimeType('image/jpg')
}); .setImage(albedoJpg);
albedoTexture.setImage(albedoPng);
material.setBaseColorTexture(albedoTexture); material.setBaseColorTexture(albedoTexture);
if (textures.baseRMMap) {
const RMJpg = await sharp(
await readTexture(
`${DATA}/${DOI['3d']}/${dirname(
parameters.resourcesPath.blitzModelPath,
)}/${textures.baseRMMap}`,
),
)
.raw()
.toBuffer({ resolveWithObject: true })
.then(({ data, info }) => {
/**
* RM mods
*
* green channel --> 1 : metallicness --> blue (interpreted as metallicness)
* green channel --> 1 - x : roughness --> green (interpreted as roughness)
* red channel --> 0 : unknown --> red (unknown)
*/
const newImage = Buffer.from(data);
for (let index = 0; index < data.length; index += 3) {
const G = newImage[index + 1];
newImage[index] = 0;
newImage[index + 1] = 255 - G;
newImage[index + 2] = G;
}
return sharp(newImage, { raw: info }).jpeg().toBuffer();
});
const RMTexture = document
.createTexture(node.materialName)
.setMimeType('image/jpg')
.setImage(RMJpg);
material.setMetallicRoughnessTexture(RMTexture);
}
if (textures.baseNormalMap) {
const normalJpg = await sharp(
await readTexture(
`${DATA}/${DOI['3d']}/${dirname(
parameters.resourcesPath.blitzModelPath,
)}/${textures.baseNormalMap}`,
),
)
.raw()
.toBuffer({ resolveWithObject: true })
.then(({ data, info }) => {
/**
* normal mods
*
* red channel --> blue channel
* green channel --> green channel
* blue channel --> red channel
*/
const newImage = Buffer.from(data);
for (let index = 0; index < data.length; index += 3) {
const R = newImage[index];
const G = newImage[index + 1];
const B = newImage[index + 2];
newImage[index] = B;
newImage[index + 1] = G;
newImage[index + 2] = R;
}
return sharp(newImage, { raw: info }).jpeg().toBuffer();
});
const normalTexture = document
.createTexture(node.materialName)
.setMimeType('image/jpg')
.setImage(normalJpg);
material.setNormalTexture(normalTexture);
}
} }
materials.set(node['#id'].readBigUInt64LE(), material); materials.set(node['#id'].readBigUInt64LE(), material);
@@ -802,10 +869,20 @@ if (allTargets || targets?.includes('tankModels')) {
}); });
} }
parseHierarchies(sc2['#hierarchy'], scene); // rotate 90 degrees on the x axis
writeFile('test.model.glb', await new NodeIO().writeBinary(document)); const rootNode = document
writeFile('test.sc2.json', JSON.stringify(sc2, null, 2)); .createNode()
writeFile('test.scg.json', JSON.stringify(scg, null, 2)); .setRotation([Math.cos(Math.PI / 4), 0, 0, -Math.sin(Math.PI / 4)]);
scene.addChild(rootNode);
parseHierarchies(sc2['#hierarchy'], rootNode);
// writeFile(
// `dist/assets/models/${id}.glb`,
// JSON.stringify((await new NodeIO().writeJSON(document)).json),
// );
await mkdir(`dist/assets/models/${id}`, { recursive: true });
nodeIO.write(`dist/assets/models/${id}/index.gltf`, document);
} }
}), }),
); );
+21
View File
@@ -0,0 +1,21 @@
import { existsSync } from 'fs';
import { readDVPLFile } from '../blitz/readDVPLFile';
import { ToJpgOptions, toJpg } from './toJpg';
export async function readTexture(
path: string,
options?: Partial<Omit<ToJpgOptions, 'format'>>,
) {
const ddsTexturePath = path.replace('.tex', '.dx11.dds.dvpl');
const isDds = existsSync(ddsTexturePath);
const resolvedTexturePath = isDds
? ddsTexturePath
: ddsTexturePath.replace('.dds', '.pvr');
const decompressedTexture = await readDVPLFile(resolvedTexturePath);
const jpg = await toJpg(decompressedTexture, {
...options,
format: isDds ? 'dds' : 'pvr',
});
return jpg;
}
+54
View File
@@ -0,0 +1,54 @@
import { exec } from 'child_process';
import { mkdir, readFile, rm, writeFile } from 'fs/promises';
import { generateUUID } from 'three/src/math/MathUtils';
import { promisify } from 'util';
const execPromise = promisify(exec);
type Channel = 'red' | 'green' | 'blue' | 'alpha';
export interface ToJpgOptions {
format: string;
swap: [Channel, Channel][];
invert: boolean;
}
const toJpgDefaultOptions: ToJpgOptions = {
format: 'dds',
swap: [],
invert: false,
};
export async function toJpg(
inputBuffer: Buffer,
options?: Partial<ToJpgOptions>,
) {
const mergedOptions = { ...toJpgDefaultOptions, ...options };
const id = generateUUID();
const input = `temp/toJpg/${id}.${mergedOptions.format}`;
const output = `temp/toJpg/${id}.jpg`;
const command = `PVRTexToolCLI -noout -i ${input} ${
mergedOptions.swap.length === 0
? ''
: mergedOptions.swap
.map(
(swapChannels) => `-${swapChannels[1]} ${input},${swapChannels[0]}`,
)
.join(' ')
} ${
mergedOptions.invert ? `-diff ${input},DIFFINVERT,255` : ''
} -d ${output}`;
console.log(command);
await mkdir('temp/toJpg', { recursive: true });
await writeFile(`temp/toJpg/${id}.${mergedOptions.format}`, inputBuffer);
await execPromise(command);
const jpgBuffer = await readFile(`temp/toJpg/${id}.jpg`);
rm(`temp/toJpg/${id}.${mergedOptions.format}`);
rm(`temp/toJpg/${id}.jpg`);
return jpgBuffer;
}
-38
View File
@@ -1,38 +0,0 @@
import { exec } from 'child_process';
import { mkdir, readFile, rm, writeFile } from 'fs/promises';
import { generateUUID } from 'three/src/math/MathUtils';
import { promisify } from 'util';
const execPromise = promisify(exec);
interface ToPngOptions {
format: string;
alpha: boolean;
}
const toPngDefaultOptions: ToPngOptions = {
format: 'dds',
alpha: true,
};
export async function toPng(
inputBuffer: Buffer,
options?: Partial<ToPngOptions>,
) {
const mergedOptions = { ...toPngDefaultOptions, ...options };
const id = generateUUID();
await mkdir('temp/toPng', { recursive: true });
await writeFile(`temp/toPng/${id}.${mergedOptions.format}`, inputBuffer);
await execPromise(
`PVRTexToolCLI -noout -i temp/toPng/${id}.${mergedOptions.format} -f ${
mergedOptions.alpha ? 'r8g8b8a8' : 'r8g8b8'
},UB,sRGB -d temp/toPng/${id}.png`,
);
const pngBuffer = await readFile(`temp/toPng/${id}.png`);
rm(`temp/toPng/${id}.${mergedOptions.format}`);
rm(`temp/toPng/${id}.png`);
return pngBuffer;
}
+8 -12
View File
@@ -14,11 +14,7 @@ type DataNode = {
qualityGroup?: string; qualityGroup?: string;
fxName?: string; fxName?: string;
textures?: { textures?: Textures;
albedo: string;
decalmask: string;
normalmap: string;
};
} & ( } & (
| ({ | ({
configCount: number; configCount: number;
@@ -70,15 +66,15 @@ interface TextureSampleStates {
normalmap: number; normalmap: number;
} }
interface Textures { export interface Textures {
albedo: string; albedo: string;
baseColorMap: string; baseColorMap?: string;
baseNormalMap: string; baseNormalMap?: string;
baseRMMap: string; baseRMMap?: string;
decalmask?: string; decalmask?: string;
maskMap: string; maskMap?: string;
miscMap: string; miscMap?: string;
normalmap: string; normalmap?: string;
} }
export interface Hierarchy { export interface Hierarchy {