Initial commit: Minecraft 光追着色器包(从零实现的 path tracing)
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
// MinecraftPT texture generator
|
||||
// Generates noise.png (blue noise), CloudNoise 3D bins, and ripple.png
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const zlib = require('zlib');
|
||||
|
||||
const outDir = path.join(__dirname, 'texture');
|
||||
fs.mkdirSync(outDir, { recursive: true });
|
||||
|
||||
// ---------- PNG encoder (minimal, non-interlaced RGBA8) ----------
|
||||
function crc32(buf) {
|
||||
let table = crc32.table;
|
||||
if (!table) {
|
||||
table = crc32.table = new Int32Array(256);
|
||||
for (let n = 0; n < 256; n++) {
|
||||
let c = n;
|
||||
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
||||
table[n] = c;
|
||||
}
|
||||
}
|
||||
let crc = -1;
|
||||
for (let i = 0; i < buf.length; i++) crc = (crc >>> 8) ^ table[(crc ^ buf[i]) & 0xff];
|
||||
return (crc ^ -1) >>> 0;
|
||||
}
|
||||
|
||||
function chunk(type, data) {
|
||||
const len = Buffer.alloc(4);
|
||||
len.writeUInt32BE(data.length);
|
||||
const typeBuf = Buffer.from(type, 'ascii');
|
||||
const crc = Buffer.alloc(4);
|
||||
crc.writeUInt32BE(crc32(Buffer.concat([typeBuf, data])));
|
||||
return Buffer.concat([len, typeBuf, data, crc]);
|
||||
}
|
||||
|
||||
function encodePNG(width, height, rgba) {
|
||||
const sig = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
||||
const ihdr = Buffer.alloc(13);
|
||||
ihdr.writeUInt32BE(width, 0);
|
||||
ihdr.writeUInt32BE(height, 4);
|
||||
ihdr[8] = 8; // bit depth
|
||||
ihdr[9] = 6; // color type RGBA
|
||||
const raw = Buffer.alloc((width * 4 + 1) * height);
|
||||
for (let y = 0; y < height; y++) {
|
||||
raw[y * (width * 4 + 1)] = 0; // filter none
|
||||
rgba.copy(raw, y * (width * 4 + 1) + 1, y * width * 4, (y + 1) * width * 4);
|
||||
}
|
||||
const idat = zlib.deflateSync(raw, { level: 9 });
|
||||
return Buffer.concat([sig, chunk('IHDR', ihdr), chunk('IDAT', idat), chunk('IEND', Buffer.alloc(0))]);
|
||||
}
|
||||
|
||||
// ---------- Hashing ----------
|
||||
function hash1(x, y, z, seed) {
|
||||
let h = (x * 374761393 + y * 668265263 + z * 2246822519 + seed * 3266489917) | 0;
|
||||
h = (h ^ (h >>> 13)) | 0;
|
||||
h = Math.imul(h, 1274126177);
|
||||
h = (h ^ (h >>> 16)) >>> 0;
|
||||
return h / 4294967296;
|
||||
}
|
||||
|
||||
// ---------- 1. Blue noise 128x128 ----------
|
||||
{
|
||||
const S = 128;
|
||||
const buf = Buffer.alloc(S * S * 4);
|
||||
const noise = new Float32Array(S * S);
|
||||
|
||||
// white noise
|
||||
for (let y = 0; y < S; y++)
|
||||
for (let x = 0; x < S; x++)
|
||||
noise[y * S + x] = hash1(x, y, 7, 12345);
|
||||
|
||||
// high-pass: n - blur(n), renormalize -> bluish
|
||||
const blurred = new Float32Array(S * S);
|
||||
for (let y = 0; y < S; y++) {
|
||||
for (let x = 0; x < S; x++) {
|
||||
let sum = 0;
|
||||
for (let dy = -1; dy <= 1; dy++)
|
||||
for (let dx = -1; dx <= 1; dx++) {
|
||||
const xx = (x + dx + S) % S;
|
||||
const yy = (y + dy + S) % S;
|
||||
sum += noise[yy * S + xx];
|
||||
}
|
||||
blurred[y * S + x] = sum / 9;
|
||||
}
|
||||
}
|
||||
let min = 1e9, max = -1e9;
|
||||
for (let i = 0; i < S * S; i++) {
|
||||
const v = noise[i] - blurred[i];
|
||||
noise[i] = v;
|
||||
if (v < min) min = v;
|
||||
if (v > max) max = v;
|
||||
}
|
||||
for (let y = 0; y < S; y++) {
|
||||
for (let x = 0; x < S; x++) {
|
||||
const v = (noise[y * S + x] - min) / (max - min);
|
||||
const p = y * S * 4 + x * 4;
|
||||
buf[p] = Math.round(v * 255);
|
||||
buf[p + 1] = Math.round(v * 255);
|
||||
buf[p + 2] = Math.round(v * 255);
|
||||
buf[p + 3] = 255;
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(path.join(outDir, 'noise.png'), encodePNG(S, S, buf));
|
||||
console.log('noise.png generated');
|
||||
}
|
||||
|
||||
// ---------- 2. 3D value noise (128^3 RGBA8) ----------
|
||||
function make3DNoise(W, H, D, seed) {
|
||||
const data = Buffer.alloc(W * H * D * 4);
|
||||
|
||||
// Generate 3 octaves of value noise at lattice 8, 4, 2
|
||||
const octaves = [
|
||||
{ lat: 8, amp: 0.6 },
|
||||
{ lat: 4, amp: 0.3 },
|
||||
{ lat: 2, amp: 0.1 },
|
||||
];
|
||||
|
||||
for (let z = 0; z < D; z++) {
|
||||
for (let y = 0; y < H; y++) {
|
||||
for (let x = 0; x < W; x++) {
|
||||
let v = 0;
|
||||
for (const o of octaves) {
|
||||
const L = o.lat;
|
||||
const fx = (x / W) * L;
|
||||
const fy = (y / H) * L;
|
||||
const fz = (z / D) * L;
|
||||
const x0 = Math.floor(fx), y0 = Math.floor(fy), z0 = Math.floor(fz);
|
||||
const x1 = x0 + 1, y1 = y0 + 1, z1 = z0 + 1;
|
||||
const tx = fx - x0, ty = fy - y0, tz = fz - z0;
|
||||
const sx = tx * tx * (3 - 2 * tx);
|
||||
const sy = ty * ty * (3 - 2 * ty);
|
||||
const sz = tz * tz * (3 - 2 * tz);
|
||||
|
||||
const c000 = hash1(x0, y0, z0, seed + o.lat);
|
||||
const c100 = hash1(x1, y0, z0, seed + o.lat);
|
||||
const c010 = hash1(x0, y1, z0, seed + o.lat);
|
||||
const c110 = hash1(x1, y1, z0, seed + o.lat);
|
||||
const c001 = hash1(x0, y0, z1, seed + o.lat);
|
||||
const c101 = hash1(x1, y0, z1, seed + o.lat);
|
||||
const c011 = hash1(x0, y1, z1, seed + o.lat);
|
||||
const c111 = hash1(x1, y1, z1, seed + o.lat);
|
||||
|
||||
const x00 = c000 + (c100 - c000) * sx;
|
||||
const x10 = c010 + (c110 - c010) * sx;
|
||||
const x01 = c001 + (c101 - c001) * sx;
|
||||
const x11 = c011 + (c111 - c011) * sx;
|
||||
const y0v = x00 + (x10 - x00) * sy;
|
||||
const y1v = x01 + (x11 - x01) * sy;
|
||||
v += (y0v + (y1v - y0v) * sz) * o.amp;
|
||||
}
|
||||
const idx = (z * H + y) * W * 4 + x * 4;
|
||||
data[idx] = Math.round(v * 255);
|
||||
data[idx + 1] = Math.round(v * 255);
|
||||
data[idx + 2] = Math.round(v * 255);
|
||||
data[idx + 3] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
{
|
||||
const W = 128, H = 128, D = 128;
|
||||
const data = make3DNoise(W, H, D, 4242);
|
||||
fs.writeFileSync(path.join(outDir, 'CloudNoise_128_128_128.bin'), data);
|
||||
console.log('CloudNoise_128_128_128.bin generated');
|
||||
}
|
||||
|
||||
{
|
||||
const W = 32, H = 32, D = 32;
|
||||
const data = make3DNoise(W, H, D, 7777);
|
||||
// RGB8 only
|
||||
const rgb = Buffer.alloc(W * H * D * 3);
|
||||
for (let i = 0; i < W * H * D; i++) {
|
||||
rgb[i * 3] = data[i * 4];
|
||||
rgb[i * 3 + 1] = data[i * 4 + 1];
|
||||
rgb[i * 3 + 2] = data[i * 4 + 2];
|
||||
}
|
||||
fs.writeFileSync(path.join(outDir, 'CloudNoise2_32_32_32.bin'), rgb);
|
||||
console.log('CloudNoise2_32_32_32.bin generated');
|
||||
}
|
||||
|
||||
// ---------- 3. Ripple 128x128 ----------
|
||||
{
|
||||
const S = 128;
|
||||
const buf = Buffer.alloc(S * S * 4);
|
||||
for (let y = 0; y < S; y++) {
|
||||
for (let x = 0; x < S; x++) {
|
||||
const dx = (x - S / 2) / S;
|
||||
const dy = (y - S / 2) / S;
|
||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||
// concentric ripple
|
||||
const n = hash1(x, y, 11, 999);
|
||||
const ripple = 0.5 + 0.5 * Math.sin(dist * 40 + n * 6.28);
|
||||
const p = (y * S + x) * 4;
|
||||
buf[p] = Math.round(ripple * 255);
|
||||
buf[p + 1] = Math.round(ripple * 255);
|
||||
buf[p + 2] = 128;
|
||||
buf[p + 3] = 255;
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(path.join(outDir, 'ripple.png'), encodePNG(S, S, buf));
|
||||
console.log('ripple.png generated');
|
||||
}
|
||||
|
||||
console.log('All textures generated in', outDir);
|
||||
Reference in New Issue
Block a user