mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
feat: pbr
This commit is contained in:
1 parent
0627f678ea
commit
d60a102c0d
6 files changed
+201
-90
No files matched your search
Vendored
+1
@@ -36,6 +36,7 @@
|
|||||||
"krieg",
|
"krieg",
|
||||||
"Leaderboard",
|
"Leaderboard",
|
||||||
"leaderboards",
|
"leaderboards",
|
||||||
|
"metallicness",
|
||||||
"MURICA",
|
"MURICA",
|
||||||
"nmatname",
|
"nmatname",
|
||||||
"noout",
|
"noout",
|
||||||
|
|||||||
+117
-40
@@ -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);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
@@ -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 {
|
||||||
|
|||||||
Reference in new issue
Block a user