pbrtools
Version:
laya pbr tools
1,293 lines (1,206 loc) • 39.7 kB
JavaScript
//@ts-check
'use strict';
var fs = require('fs');
var parsehdr = require('./parse-hdr');
var PNG = require('pngjs').PNG;
//var images = require('images');
var TGA = require('tga');
var path = require('path');
if(process.argv[0].indexOf('electron')!=-1){
throw '暂时不支持electron';
}
var ctools= require('./build/Release/envprefilter_'+process.platform+'_'+process.versions.modules);
/**
* @param data {Uint8Array}
* @param w {number}
* @param h {number}
* @param file {string}
*/
function saveaspng(data,w,h,file){
var png = new PNG({ filterType:4,
width:w,
height:h,
inputHasAlpha:true
});
png.data=new Buffer(data);
png.pack()
.pipe(fs.createWriteStream(file))
.on('finish', function() {
console.log('Written!');
});
}
/**
* @param data {Uint8Array}
* @param w {number}
* @param h {number}
* @param file {string}
* @param onend {Function=}
*/
function saveasraw(data,w,h,file,onend){
try{
var ws = fs.createWriteStream(file);
var size = new Buffer(8);
size.writeUInt32LE(w,0);
size.writeUInt32LE(h,4);
ws.write(size);
ws.write(new Buffer(data));
if(onend)
ws.on('close',onend);
ws.end();
}catch(e){
console.log('saveasraw error:'+file);
}
}
/*
var dt = new Uint8ClampedArray(128*128*4);
var idx=0;
for (var y = 0; y < 128; y++) {
for (var x = 0; x <128; x++) {
dt[idx++] = 0xff;
dt[idx++] = 0xff;
dt[idx++] = 0;
dt[idx++] = 0x77;
}
}
saveaspng(dt,128,128,'');
*/
//png.pack().pipe(fs.createWriteStream('out.png'));
function hdrToRaw(fpath){
}
/**
* 分解一个浮点数。
* 返回 [尾数,指数],
* 例如 frexp(16.4) = [0.5125,5] , 0.512*pow(2,5)=16.4
*/
function frexp(value) {
if (value === 0) return [value, 0];
var data = new DataView(new ArrayBuffer(8));
data.setFloat64(0, value);
var bits = (data.getUint32(0) >>> 20) & 0x7FF;
if (bits === 0) { // denormal
data.setFloat64(0, value * Math.pow(2, 64)); // exp + 64
bits = ((data.getUint32(0) >>> 20) & 0x7FF) - 64;
}
var exponent = bits - 1022;
var mantissa = ldexp(value, -exponent);
return [mantissa, exponent];
}
/**
* 合并一个浮点数
*/
function ldexp(mantissa, exponent) {
var steps = Math.min(3, Math.ceil(Math.abs(exponent) / 1023));
var result = mantissa;
for (var i = 0; i < steps; i++)
result *= Math.pow(2, Math.floor((exponent + i) / steps));
return result;
}
//console.log(frexp(16.4)); = 0.5125, 5
//var retBuf = new Uint8ClampedArray(4);
/**
* 把颜色转成rgbe格式。输出范围都是0~255
* @param red {number}
* @param green {number}
* @param blue {number}
* @param retBuf {Uint8ClampedArray}
*/
function float2rgbe(red, green, blue,retBuf){
var v = red;
if (green > v) v = green;
if (blue > v) v = blue;
if (v < 1e-32) {
retBuf[0] = retBuf[1] = retBuf[2] = retBuf[3] = 0;
}
else {
var e = frexp(v);
v = e[0] * 256.0/v; //当某个值为v的时候,其尾数就是e[0]。 这里*256了,所以反向的时候有个/256即-(128+8)里的8
//e[0]永远不会为1所以结果<256
retBuf[0] = (red * v);
retBuf[1] = (green * v);
retBuf[2] = (blue * v);
retBuf[3] = (e[1] + 128);
}
return retBuf;
}
/**
* @param r {number} r 0~255
* @param g {number} g
* @param b {number} b
* @param e {number} alpha
* @param outc {Float32Array}
*/
function rgbe2rgb(r,g,b,e, outc){
var f = Math.pow(2.0, e - 128.0-8.0);
outc[0]=r*f;
outc[1]=g*f;
outc[2]=b*f;
}
function hdrToPng(fpath){
//var testpath = 'F:/threejspbr/imgs/env/AtticRoom/';
for(var i=0; i<=10; i++){
var cf = fpath+'env_'+i+'.hdr';
console.log(cf);
var buff = fs.readFileSync(cf);
var img = parsehdr(buff,false);
//console.log(img.shape);
//console.log(img.exposure);
//console.log(img.gamma);
var w = img.shape[0];
var h = img.shape[1];
//saveaspng(img.data,w,h,cf+'.png');
saveasraw(img.data,w,h,cf+'.raw');
}
}
//hdrToPng('F:/work/pbr/threejspbr/imgs/env/AtticRoom/');
/**
* @param buf {Uint8Array}
* @param w {number}
* @param h {number}
* @return {Float32Array}
*/
function rgbebuf_to_floatbuf(buf,w,h){
var ret = new Float32Array(w*h*3);
var cfi=0;
var cii=0;
var num = w*h;
for(var i=0; i<num; i++){
var r = buf[cii++];
var g = buf[cii++];
var b = buf[cii++];
var e = buf[cii++];
var f = Math.pow(2.0, e - 128.0-8.0);
ret[cfi++]=r*f;
ret[cfi++]=g*f;
ret[cfi++]=b*f;
}
return ret;
}
/**
* @param buf {Float32Array}
* @param w {number}
* @param h {number}
* @return {Uint8Array}
*/
function floatbuf_to_rgbebuf(buf, w, h){
}
/**
* @param buf {Float32Array}
* @param w {number}
* @param h {number}
*/
function genmipmapnextlv_float3(buf,w,h){
if(w==1 && h==1)
return buf;
}
/**
* @param buf {Uint8Array}
* @param w {number}
* @param h {number}
* @return {Uint8Array}
*/
function genmipmapnextlv_rgbe(buf, w,h){
var fbuf = genmipmapnextlv_float3(rgbebuf_to_floatbuf(buf,w,h),w,h);
return floatbuf_to_rgbebuf(fbuf,w,h);
}
/**
* 所有的合并成一个,开始记录0级的宽高,数据直到1x1
*/
function hdrtomipmap(fpath,outfile){
var ws = fs.createWriteStream(outfile);
var cw=0;
var ch=0;
var curbuf;
for(var i=0; i<=10; i++){
var cf = fpath+'env_'+i+'.hdr';
console.log(cf);
var buff = fs.readFileSync(cf);
var img = parsehdr(buff,false);
var w = img.shape[0];
var h = img.shape[1];
if(i==0){
var size = new Buffer(8);
size.writeUInt32LE(w,0);
size.writeUInt32LE(h,4);
ws.write(size);
cw=w;
ch=h;
}else{
cw/=2;
ch/=2;
}
//saveaspng(img.data,w,h,cf+'.png');
curbuf = img.data;
ws.write(new Buffer(img.data));
}
//还差一层
if(cw==2&&ch==1){
console.log('还差一层。补上');
console.log(curbuf);
var fbuf = rgbebuf_to_floatbuf(curbuf,2,1);
console.log(fbuf);
fbuf[0]=(fbuf[0]+fbuf[3])/2.0;
fbuf[1]=(fbuf[1]+fbuf[4])/2.0;
fbuf[2]=(fbuf[2]+fbuf[5])/2.0;
var lastlv = new Uint8ClampedArray(4);
console.log(fbuf);
float2rgbe(fbuf[0],fbuf[1],fbuf[2],lastlv);
ws.write(new Buffer(lastlv));
//ws.write()
}else{
throw '大小不对';
}
ws.end();
}
//var testpath = 'F:/work/pbr/threejspbr/imgs/env/AtticRoom/';
var testpath = 'E:/layaair/layaair/publish/LayaAirPublish/samples/as/3d/bin/h5/threeDimen/env/inthegarden/';
//hdrtomipmap('F:/work/pbr/threejspbr/imgs/env/AtticRoom/','F:/work/pbr/threejspbr/imgs/env/AtticRoom/mipmaps.mm');
/* TODO 那这个测试mipmap生成
console.log(rgbebuf_to_floatbuf((new Uint8Array([0xB5,0x99,0x6a,0x80,0x98,0x85,0x66,0x80,0x94,0x8a,0x77,0x81,0x98,0x88,0x6c,0x81,
0x84,0x74,0x68,0x7f,0x87,0x82,0x82,0x7f,0xa4,0xa0,0xa0,0x80,0x92,0x88,0x81,0x80
])),4,2));
*/
/**
* @param fpath {string}
*/
function pngtoraw(fpath){
for(var i=0; i<=10; i++){
var cf = testpath+'env_'+i+'.hdr.png';
console.log(cf);
fs.createReadStream(cf)
.pipe(new PNG({
filterType: 4
}))
.on('parsed', function() {
for (var y = 0; y < this.height; y++) {
for (var x = 0; x < this.width; x++) {
var idx = (this.width * y + x) << 2;
// invert color
this.data[idx] = 255 - this.data[idx];
this.data[idx+1] = 255 - this.data[idx+1];
this.data[idx+2] = 255 - this.data[idx+2];
// and reduce opacity
this.data[idx+3] = this.data[idx+3] >> 1;
}
}
});
}
}
class _Pixel{
/**
* @param r {number}
* @param g {number}
* @param b {number}
*/
constructor(r,g,b){
this.rgb = new Float32Array([r,g,b]);
}
/**
* @return {number}
*/
get r(){
return this.rgb[0];
}
get x(){
return this.rgb[0];
}
/**
* @param v {number}
*/
set r(v){
this.rgb[0]=v;
}
/**
* @return {number}
*/
get g(){
return this.rgb[1];
}
get y(){
return this.rgb[1];
}
/**
* @param v {number}
*/
set g(v){
this.rgb[1]=v;
}
/**
* @return {number}
*/
get b(){
return this.rgb[2];
}
get z(){
return this.rgb[2];
}
/**
* @param v {number}
*/
set b(v){
this.rgb[2]=v;
}
/**
* @param o {_Pixel}
*/
copy(o){
this.rgb[0]=o.rgb[0];
this.rgb[1]=o.rgb[1];
this.rgb[2]=o.rgb[2];
return this;
}
mul(f){
this.rgb[0]*=f;
this.rgb[1]*=f;
this.rgb[2]*=f;
return this;
}
add(v){
this.rgb[0]+=v.rgb[0];
this.rgb[1]+=v.rgb[1];
this.rgb[2]+=v.rgb[2];
return this;
}
}
/**
* @param v1 {_Pixel}
* @param v2 {_Pixel}
* @return {_Pixel}
*/
_Pixel.cross=function(v1,v2){
var x = v1.y*v2.z-v1.z*v2.y;
var y = v1.z*v2.x-v1.x*v2.z;
var z = v1.x*v2.y-v1.y*v2.x;
return new _Pixel(x,y,z);
};
/**
* @param v1 {_Pixel}
* @return {_Pixel}
*/
_Pixel.normalize = function(v1){
var len = Math.sqrt(v1.x*v1.x+v1.y*v1.y+v1.z*v1.z);
if(len<1e-6)
return new _Pixel(0,0,0);
return new _Pixel(v1.x/len,v1.y/len,v1.z/len);
};
/**
* 加载图片。保存成float32的rgb通道和alpha通道。值为0.0~1.0
* 使用例子:
* 1. 加载
* let img = new TextureBase(null,0,0);
* img.onloadEnd=()=>{
* }
* img.load(url);
*
* 2. 获取某个通道
* 加载后
* img.extractCh('r');
*
*/
class TextureBase{
/**
* @param fbuf {Float32Array}
* @param w {number}
* @param h {number}
*/
constructor(fbuf,w,h){
this.buff = fbuf;
this.width = w;
this.height = h;
//
this.alphaCh = null;
this.src='';
}
/**
* @param u {number}
* @param v {number}
* @return {_Pixel}
*/
getPixel(u,v){
var x = Math.floor(u*this.width+0.5);
var y = Math.floor(v*this.height+0.5);
var p = (y*this.width+x)*3;
return new _Pixel(this.buff[p], this.buff[p+1], this.buff[p+2]);
}
onloadEnd(){
}
loadFromPng(file){
var me = this;
fs.createReadStream(file)
.pipe(new PNG({
filterType: 4
}))
.on('parsed', function() {
me.buff = new Float32Array(this.width*this.height*3);
me.alphaCh = new Float32Array(this.width*this.height);
me.width=this.width;
me.height=this.height;
var bufi=0;
var alphai=0;
for (var y = 0; y < this.height; y++) {
for (var x = 0; x < this.width; x++) {
var idx = (this.width * y + x) << 2;
// invert color
var r = this.data[idx];
var g = this.data[idx+1];
var b = this.data[idx+2];
me.buff[bufi++]=r/255.0;
me.buff[bufi++]=g/255.0;
me.buff[bufi++]=b/255.0;
// and reduce opacity
me.alphaCh[alphai++] = this.data[idx+3]/255.0;
}
}
me.onloadEnd();
});
}
/**
* 简单归一化
*/
normalize(){
var ii=0;
var maxv=0.0;
for(var i=0; i<this.width*this.height; i++){
var r = this.buff[ii++];
var g = this.buff[ii++];
var b = this.buff[ii++];
maxv=Math.max(maxv,r);
maxv=Math.max(maxv,g);
maxv=Math.max(maxv,b);
}
ii=0;
this.buff.forEach((v,i)=>{
this.buff[i]=v/maxv;
});
return this;
}
/**
* @param file {string}
*/
load(file){
this.src=file;
var path = require('path');
var ext = path.extname(file).toLowerCase();
if( ext=='.hdr'){
this.loadFromHDR(file);
}else if(ext=='.png'){
this.loadFromPng(file);
}else if(ext=='.tga'){
this.loadFromTGA(file);
}else if(ext=='.raw'){
this.loadFromRaw(file);
}else{
throw 'unsupported format:'+file;
}
}
loadFromHDR(file){
console.log('load hdrfile '+file);
var buff = fs.readFileSync(file);
if(!buff){
throw 'read file error!';
}
var img = parsehdr(buff,false);
var w = img.shape[0];
var h = img.shape[1];
this.width=w;
this.height=h;
this.buff = new Float32Array(w*h*3);
var bi=0;
var srci=0;
for( var y=0; y<h; y++){
for(var x=0; x<w; x++){
var r = img.data[srci++];
var g = img.data[srci++];
var b = img.data[srci++];
var e = img.data[srci++];
var f = Math.pow(2.0, e - 128.0-8.0);
this.buff[bi++]=r*f;
this.buff[bi++]=g*f;
this.buff[bi++]=b*f;
}
}
this.onloadEnd();
}
//width,height,hdrdata
loadFromRaw(file){
var buff = fs.readFileSync(file);
var dv = new DataView(buff.buffer);
var w = dv.getUint32(0,true);
var h = dv.getUint32(4,true);
var u8buff = new Uint8Array(buff.buffer,8);
this.width=w;
this.height=h;
this.buff = new Float32Array(w*h*3);
var bi=0;
var srci=0;
for( var y=0; y<h; y++){
for(var x=0; x<w; x++){
var r = u8buff[srci++];
var g = u8buff[srci++];
var b = u8buff[srci++];
var e = u8buff[srci++];
var f = Math.pow(2.0, e - 128.0-8.0);
this.buff[bi++]=r*f;
this.buff[bi++]=g*f;
this.buff[bi++]=b*f;
}
}
this.onloadEnd();
}
loadFromTGA(file){
var tga = new TGA(fs.readFileSync(file));
this.width=tga.width;
this.height = tga.height;
this.buff = new Float32Array( tga.width*tga.height*3);
this.alphaCh = new Float32Array( tga.width*tga.height);
var di=0,si=0,alpahchi=0;
for(var y=tga.height-1; y>=0; y--){
for(var x=0; x<tga.width; x++){
var pix=tga.pixels[y*tga.width+x];//颠倒一下
this.buff[di++]=pix.r/255;
this.buff[di++]=pix.g/255;
this.buff[di++]=pix.b/255;
this.alphaCh[alpahchi++]=pix.a/255;
}
}
this.onloadEnd();
}
/**
* 保存成png。
* @param {string} file
* @param {boolean=} addgamma true的话,表示这是一个线性图片,需要先预处理gamma,即v=pow(v,1/2.2)
*/
saveAsPng(file,addgamma){
var png = new PNG({ filterType:4,
width:this.width,
height:this.height,
inputHasAlpha:true
});
var data = new Uint8Array(this.width*this.height*4);
var oi=0;
var ii=0;
var scale = 255*1;
var alphai=0;
var gammav = 1.0;
if(addgamma)gammav = 1/2.2;
var gamma = (v)=>Math.pow(Math.min(v,1),gammav);
for(var i=0; i<this.width*this.height; i++){
var r = gamma(this.buff[ii++]);
var g = gamma(this.buff[ii++]);
var b = gamma(this.buff[ii++]);
data[oi++]=Math.min(r*scale,255);
data[oi++]=Math.min(g*scale,255);
data[oi++]=Math.min(b*scale,255);
if(this.alphaCh){
var alphav = this.alphaCh[alphai++]*scale;
alphav = Math.min(Math.max(alphav,1),255);
data[oi++]=alphav;
}else{
data[oi++]=255;
}
}
png.data=new Buffer(data);
try{
png.pack()
.pipe(fs.createWriteStream(file))
.on('finish', function() {
console.log('save end:'+file);
});
}catch(e){
console.log('save '+file+' error!');
}
}
/**
* @param file {string} 输出文件名。
* @param onend {Function=}
* @return 返回转换成rgbe格式的数据。
*/
saveAsRaw(file,onend){
var data = new Uint8Array(this.width*this.height*4);
var oi=0;
var ii=0;
var rgbe = new Uint8ClampedArray(4);
for(var i=0; i<this.width*this.height; i++){
var r = this.buff[ii++];
var g = this.buff[ii++];
var b = this.buff[ii++];
float2rgbe(r,g,b,rgbe);
data[oi++]=rgbe[0];
data[oi++]=rgbe[1];
data[oi++]=rgbe[2];
data[oi++]=rgbe[3];
}
saveasraw(data,this.width,this.height,file,onend);
console.log('save as raw:'+file);
return data;
}
/**
* 用png的形式封装hdr。
* @param {string} file
*/
saveAsHDRPng(file){
var data = new Uint8Array(this.width*this.height*4);
var oi=0;
var ii=0;
var rgbe = new Uint8ClampedArray(4);
for(var i=0; i<this.width*this.height; i++){
var r = this.buff[ii++];
var g = this.buff[ii++];
var b = this.buff[ii++];
float2rgbe(r,g,b,rgbe);
data[oi++]=rgbe[0];
data[oi++]=rgbe[1];
data[oi++]=rgbe[2];
data[oi++]=rgbe[3];
}
var png = new PNG({ filterType:4,
width:this.width,
height:this.height,
inputHasAlpha:true
});
try{
png.data = new Buffer(data);
png.pack()
.pipe(fs.createWriteStream(file))
.on('finish', function() {
console.log('save end:'+file);
});
}catch(e){
console.log('saveAsHDRPng '+file+' error!');
}
}
/**
* 取出某个通道,是复制的,可以修改
* @param c {'r'|'g'|'b'|'a'}
* @return {Float32Array}
*/
extractCh(c){
if(!this.buff) throw 'error no buffer! '+this.src;
var ch = 0;
if(c=='r')ch=0;
else if(c=='g')ch=1;
else if(c=='b')ch=2;
else if(c=='a'){
if(this.alphaCh)
return new Float32Array(this.alphaCh.buffer);
return null;
}
else throw 'bad param';
var ret = new Float32Array(this.width*this.height);
var oi=0;
for( var i=0; i<this.width*this.height; i++){
ret[oi++]=this.buff[i*3+ch];
}
return ret;
}
}
var _2PI = Math.PI*2.0;
/**
* 全景图。x轴指向中心。
*/
class PanoramaEnv extends TextureBase{
/**
* @param fbuf {Float32Array}
* @param w {number}
* @param h {number}
*/
constructor(fbuf,w,h){
super(fbuf,w,h);
}
/**
* 根据朝向进行采样
* @param x {number}
* @param y {number}
* @param z {number}
* @return {_Pixel}
*/
getPixel3(x,y,z){
var u = Math.atan2(z,x)/_2PI+0.5;
var v = Math.acos(y)/Math.PI;
//return new _Pixel(u,v,0); //TODO 这里有个错误。即图片的最上面一行的u始终为1,会有一个红边。
return this.getPixel(u,v);
}
/**
* @return {_Pixel}
*/
getNormal(u,v){
//0,0对应pi,pi/2, 1,0对应-Pi, pi/2
//var phi = (0.5-u)*2*Math.PI; //TODO 这里不知道应不应该取反
var phi = (u-0.5)*2*Math.PI;
var alpha = v*Math.PI;
var sina = Math.sin(alpha);
return new _Pixel( Math.cos(phi)*sina,Math.cos(alpha), Math.sin(phi)*sina);
}
}
//TEST
var ttt = new PanoramaEnv(null,0,0);
/**
* @param env {PanoramaEnv} 预处理环境贴图,生成各层的png,raw和总的mipmaps
* @param maxlv {number} 这一级对应最大粗糙度。因为1x1的像素其实没意义
* @param outpath {string?} 输出目录
*/
let prefilter = exports.prefilter=function(env,maxlv,outpath){
var outdir = outpath || 'd:/temp/env/' ;
new PanoramaEnv(env.buff,env.width,env.height).saveAsPng(outdir+'env_0.hdr.png');
var lvdata = new PanoramaEnv(env.buff,env.width,env.height).saveAsRaw(outdir+'env_0.hdr.raw');
//把所有的raw合并成一个mipmaps文件
var mipmapsf = fs.createWriteStream( path.resolve(outdir,'env.mipmaps'));
var rawsize = new Buffer(8);
rawsize.writeUInt32LE(env.width,0);
rawsize.writeUInt32LE(env.height,4);
mipmapsf.write(rawsize);//先写第一层的大小
mipmapsf.write(new Buffer(lvdata));//原始数据
var cw = env.width;
var ch = env.height;
var cbuff = env.buff;
var dcw=cw,dch=ch;
var stop = false;
var down=true;
var tst = Date.now();
var dts = [];
for( var i=1; !stop && i<=10; i++){
if(down){
dcw = cw/2; if(dcw<1)dcw=1;
dch = ch/2; if(dch<1)dch=1;
}
if(dcw==1||dch==1) stop=true;//下一次
console.log('wh='+cw+','+ch+',cwh='+dcw+','+dch);
var downbufsz = dcw*dch*3;
var downbuff = new Float32Array(downbufsz);
if(down){
ctools.nextlevel(cbuff,cw,ch,downbuff);
downbuff.set(cbuff.buffer);
}else{
downbuff = cbuff;
}
cbuff = downbuff;
var obuff = new Float32Array(downbufsz);
var fst = Date.now();
//ctools.prefilter(downbuff,dcw,dch,Math.sqrt(0.1*i),obuff);
ctools.prefilter(downbuff,dcw,dch,Math.min(1,i/maxlv),obuff);//sqrt本身也没什么依据,且会导致粗糙度分布不均
dts.push(Date.now()-fst);//统计时间
var outpe = new PanoramaEnv(obuff,dcw,dch);
lvdata = outpe.saveAsRaw(outdir+'env_'+i+'.hdr.raw');
mipmapsf.write(new Buffer(lvdata));
outpe.saveAsPng(outdir+'env_'+i+'.hdr.png');
cw = dcw;
ch = dch;
}
//还差一层
if(dcw==2&&dch==1){
console.log('还差一层。补上');
var fbuf = new Float32Array(3);
fbuf[0]=(cbuff[0]+cbuff[3])/2.0;
fbuf[1]=(cbuff[1]+cbuff[4])/2.0;
fbuf[2]=(cbuff[2]+cbuff[5])/2.0;
var lastlv = new Uint8Array(4);
console.log(fbuf);
float2rgbe(fbuf[0],fbuf[1],fbuf[2],lastlv);
mipmapsf.write(new Buffer(lastlv));
}else{
//throw 'cur level size error:'+dcw+','+dch;
}
mipmapsf.end();
console.log('totaltm='+(Date.now()-tst)+',tms='+dts);
};
function prefiltertest(env,lv){
var outdir = 'd:/temp/env/';
new PanoramaEnv(env.buff,env.width,env.height).saveAsPng(outdir+'env_0.hdr.png');
var tst = Date.now();
var dts = [];
for( var i=1; i<lv; i++){
var fst = Date.now();
var obuff = new Float32Array(env.width*env.height*3);
ctools.prefilter(env.buff,env.width,env.height,Math.sqrt(i/lv),obuff);
dts.push(Date.now()-fst);//统计时间
var outpe = new PanoramaEnv(obuff,env.width,env.height);
outpe.saveAsPng(outdir+'env_'+i+'.hdr.png');
}
console.log('totaltm='+(Date.now()-tst)+',tms='+dts);
}
/**
* @param env {PanoramaEnv}
* @param normalize {boolean}
* @param onend {Function=}
* @return {string}
*/
function prefilterDiff(env,normalize,onend){
var sz = env.width*env.height*3;
var obuff = new Float32Array(sz);
ctools.prefilterDiff(env.buff,env.width,env.height, obuff,1);
var outp = new PanoramaEnv(obuff,env.width,env.height);
normalize && outp.normalize();
outp.saveAsPng('d:/temp/env/envxx.png');
outp.saveAsRaw('d:/temp/env/envxx.raw',onend);
return 'd:/temp/env/envxx.raw';
}
function testenv(env){
ctools.test1(env.buff,env.width,env.height);
new PanoramaEnv(env.buff,env.width,env.height).saveAsPng('d:/temp/env/ss.png');
}
function genCubmap(env,dir,ow,outf){
var owidth = ow;
var obuff = new Float32Array(owidth*owidth*3);
ctools.getCubmap(env.buff,env.width,env.height,dir,obuff);
new PanoramaEnv(obuff,owidth,owidth).saveAsPng(outf);
}
function normalmap_u3d_webgl(env,outf){
var obuff = new Float32Array(env.width*env.height*3);
for( var i=0; i<env.buff.length; ){
obuff[i]=env.buff[i]; i++;
var g = env.buff[i]; g=(g-0.5)*2; g=-g; g=(g+1)/2;
obuff[i]=g; i++;
obuff[i]=env.buff[i]; i++;
}
new PanoramaEnv(obuff,env.width,env.height).saveAsPng(outf);
}
/**
* 波长转rgb
* 参考 http://stackoverflow.com/questions/3407942/rgb-values-of-visible-spectrum
* @param {number} l 波长,单位是纳米
* @param {Float32Array} out 输出rgb
*/
function spectral_color(l, out ){ // RGB <0,1> <- lambda l <400,700> [nm]
var t; out[0]=0.0; out[1]=0.0; out[2]=0.0;
//红色曲线
if ((l>=400.0)&&(l<410.0)) { t=(l-400.0)/(410.0-400.0); out[0]= +(0.33*t)-(0.20*t*t); }
else if ((l>=410.0)&&(l<475.0)) { t=(l-410.0)/(475.0-410.0); out[0]=0.14 -(0.13*t*t); }
else if ((l>=545.0)&&(l<595.0)) { t=(l-545.0)/(595.0-545.0); out[0]= +(1.98*t)-( t*t); }
else if ((l>=595.0)&&(l<650.0)) { t=(l-595.0)/(650.0-595.0); out[0]=0.98+(0.06*t)-(0.40*t*t); }
else if ((l>=650.0)&&(l<700.0)) { t=(l-650.0)/(700.0-650.0); out[0]=0.65-(0.84*t)+(0.20*t*t); }
//绿色曲线
if ((l>=415.0)&&(l<475.0)) { t=(l-415.0)/(475.0-415.0); out[1]= +(0.80*t*t); }
else if ((l>=475.0)&&(l<590.0)) { t=(l-475.0)/(590.0-475.0); out[1]=0.8 +(0.76*t)-(0.80*t*t); }
else if ((l>=585.0)&&(l<639.0)) { t=(l-585.0)/(639.0-585.0); out[1]=0.84-(0.84*t) ; }
//蓝色曲线
if ((l>=400.0)&&(l<475.0)) { t=(l-400.0)/(475.0-400.0); out[2]= +(2.20*t)-(1.50*t*t); }
else if ((l>=475.0)&&(l<560.0)) { t=(l-475.0)/(560.0-475.0); out[2]=0.7 -( t)+(0.30*t*t); }
}
/*
var obuff = new ArrayBuffer(100*100*3);
console.log(ctools.prefilter(new ArrayBuffer(100*100*3),100,100,0.0,obuff));
ttt = new PanoramaEnv(obuff,100,100);
ttt.saveAsPng('d:/temp/xw.png');
*/
/*
var rgb=new Float32Array(3);
var rgbe = new Uint8ClampedArray(4);
rgbe2rgb(0xac,0x9c,0x83,0x80,rgb);
console.log(rgb);
float2rgbe(rgb[0],rgb[1],rgb[2],rgbe);
console.log(rgbe);
float2rgbe(1000,3000,100000,rgbe);
console.log(rgbe);
rgbe2rgb(rgbe[0],rgbe[1],rgbe[2],145,rgb);
console.log(rgb);
*/
/**
* 预处理环境贴图到mipmaps
* @param {string?} infile 输入文件
* @param {number?} maxlv 最粗糙的到哪一层
* @param {string?} outfile 输出文件
* @param {function(PanoramaEnv):boolean=} checkimg
*/
function prefilterenv(infile, maxlv, outpath,checkimg){
var inputfile = infile || 'e:/layaair/layaair/publish/LayaAirPublish/samples/as/3d/bin/h5/threeDimen/env/inthegarden/env512.hdr';
var maxmiplevel = maxlv || 7;
var outputpath = outpath || 'd:/temp/env/';
var pe = new PanoramaEnv(null,0,0);
pe.onloadEnd=function(){
if(checkimg && !checkimg(pe)){
return;
}
pe.saveAsPng(path.resolve(outputpath,'env.png'));
prefilter(pe,maxmiplevel, outputpath);
pe.saveAsHDRPng('d:/temp/env/env1.png');
};
pe.load(inputfile);
}
/**
* @param {string} infile
* @param {boolean} normalize
* @param {function(Float32Array):void} onend
*/
let doCalcSH= exports.doCalcSH=function(infile,normalize,onend){
var inputfile = infile || 'e:/layaair/layaair/publish/LayaAirPublish/samples/as/3d/bin/h5/threeDimen/env/inthegarden/env512.hdr';
var pe = new PanoramaEnv(null,0,0);
pe.onloadEnd=function(){
/*
pe.saveAsPng('d:/temp/ddd.png',true);
return;
*/
var outfile = prefilterDiff(pe,normalize,()=>{
var diffpe = new PanoramaEnv(null,0,0);
diffpe.onloadEnd =()=>{
var result = new Float32Array(9*3);
var rgbmat = new Float32Array(16*3);
ctools.calcSH9(diffpe.buff,diffpe.width,diffpe.height, result,rgbmat);
onend(rgbmat);
//console.log(rgbmat.join(','));
//测试,现在calcSH9会修改buffer的内容,根据系数重新恢复内容,所以保存的是重新创建的buffer
diffpe.saveAsPng('d:/temp/env/sh.png',true);//TEST
};
diffpe.load(outfile);
});
};
pe.load(inputfile);
};
/**
* 处理环境贴图,在图片所在路径下生成env.png,env.mipmaps,envinfo.json
* @param {string} img 要出里的图片的绝对路径
*/
let handleenvmap = exports.handleenvmap=function(img){
let outdir = path.dirname(img);
let infofile = path.resolve(outdir,'envinfo.json');
let jsonobj={
skytex:'env.png',
prefiltedEnv:'env.mipmaps',
IrradianceMat:[],
sunpos:[0,1,0]
};
try{
//现在先不计算太阳位置,所以可能是手写的,所以这里要保留原来的值。
//以后自动计算的时候,就直接替换就行,不用再打开原始文件了
if(fs.exists(infofile)){
let curjsonobj = JSON.parse(fs.readFileSync(infofile,'utf8'));
if(curjsonobj.sunpos)
jsonobj.sunpos = curjsonobj.sunpos;
}
}catch(e){
//
}
prefilterenv(img,7,outdir,(pimg)=>{
if(pimg.width===512 && pimg.height===256)
return true;
console.log('只能处理固定大小的图片(512x256),这个图片不符合要求:'+pimg.width+'x'+pimg.height);
return false;
});
doCalcSH(img,false,(rgbmat)=>{
let im = jsonobj.IrradianceMat;
rgbmat.forEach((v)=>{im.push(v);});
fs.writeFileSync(infofile,JSON.stringify(jsonobj),'utf8');
});
};
function doHandleEnvTexture(){
ttt.onloadEnd=function(){
//prefilterPanorama(ttt.buff,ttt.width,ttt.height,0.3);
//prefilterDiff(ttt);
prefilter(ttt,7,null);
//prefiltertest(ttt,4);
//normalmap_u3d_webgl(ttt,'d:/temp/env/upbodyN.png');
/*
var result = new Float32Array(9*3);
var rgbmat = new Float32Array(16*3);
ctools.calcSH9(ttt.buff,ttt.width,ttt.height, result,rgbmat);
console.log(rgbmat.join(','));
ttt.saveAsPng('d:/temp/env/sh.png');//TEST
*/
//testenv(ttt);
/*
genCubmap(ttt,0,128,'d:/temp/env/0.png');
genCubmap(ttt,1,128,'d:/temp/env/1.png');
genCubmap(ttt,2,128,'d:/temp/env/2.png');
genCubmap(ttt,3,128,'d:/temp/env/3.png');
genCubmap(ttt,4,128,'d:/temp/env/4.png');
genCubmap(ttt,5,128,'d:/temp/env/5.png');
*/
ttt.saveAsHDRPng('d:/temp/env/env1.png');
/*
var ci=0;
for(var y=0; y<ttt.height; y++ ){
for(var x=0; x<ttt.width; x++){
var r = ttt.buff[ci];
r = r*2-1;
if(r<0)r=0;
ttt.buff[ci++]=r;
ttt.buff[ci++]=r;
ttt.buff[ci++]=r;
}
}
ttt.saveAsPng('E:/layaair/layaair/publish/LayaAirPublish/samples/as/3d/bin/h5/threeDimen/models/1/mf000_n_r.png');
*/
};
testpath = 'e:/layaair/layaair/publish/LayaAirPublish/samples/as/3d/bin/h5/threeDimen/env/inthegarden/env512.hdr';
//testpath = 'D:/temp/env/envxx.raw';
//testpath = 'e:/layaair/layaair/publish/LayaAirPublish/samples/as/3d/bin/h5/threeDimen/models/dude/dude/upbodyN.png';
//testpath = __dirname+'/testData/env256.hdr';
//testpath = 'E:/layaair/layaair/publish/LayaAirPublish/samples/as/3d/bin/h5/threeDimen/env/inthegarden/inthegarden.png';
//testpath = 'F:/layaair/layaair/publish/LayaAirPublish/samples/as/3d/bin/h5/threeDimen/env/inthegarden/env.png';
//ttt.loadFromPng('F:/work/pbr/threejspbr/assets/imgs/env/AtticRoom/env1.png');
//ttt.loadFromHDR('F:/work/pbr/threejspbr/imgs/env/AtticRoom/env_0.hdr');
//ttt.loadFromHDR('F:/work/pbr/threejspbr/assets/imgs/env/overcloud/env.hdr');
//ttt.loadFromPng(testpath+'env.png');
ttt.load(testpath);
}
/**
*
*/
let mergetexture = exports.mergetexture = function(basecolor,normal,pbrinfo,basecoloro,normalo,mergeao){
var basecolorimg = new TextureBase(null,0,0);
var normalimg = new TextureBase(null,0,0);
var pbrinfoimg = new TextureBase(null,0,0);
var loadi=0;
function onallend(){
var chk = basecolorimg.width==pbrinfoimg.width ;//&& normalimg.width==pbrinfoimg.width;
if(!chk) throw 'all images should have same size!';
normalimg.alphaCh = pbrinfoimg.extractCh('g');//粗糙度
//只允许normal比pbrinfo大一倍
if(normalimg.width==2*pbrinfoimg.width && normalimg.height==2*pbrinfoimg.height){
//放大一下粗糙度
var roughness = normalimg.alphaCh;
var bigrough = new Float32Array(normalimg.width*normalimg.height);
var oi=0;
for( var y=0; y<normalimg.height; y++){
for(var x=0; x<normalimg.width; x++){
var sx = Math.floor(x/2);
var sy = Math.floor(y/2);
bigrough[oi++]=roughness[sy*pbrinfoimg.width+sx];
}
}
normalimg.alphaCh = bigrough;
}else if(normalimg.width!=pbrinfoimg.width){
throw 'bad size';
}
var metaless = pbrinfoimg.extractCh('b');//金属度
if(!basecolorimg.alphaCh)
basecolorimg.alphaCh = metaless;
else{
//如果有半透明一定不是金属
var srca = basecolorimg.alphaCh;
for( var i=0; i<metaless.length; i++){
if(srca[i]<1.0){
srca[i]/=2.0; //缩小一下,保证最高位不是1。这表示这是alpha信息
}else{
srca[i]=(metaless[i]/2.0)+0.50196078431372549019607843137255;
}
}
}
//AO
if(mergeao){
var ci=0;
var aoi=0;
var ao = pbrinfoimg.extractCh('r');
for(y=0; y<basecolorimg.width; y++){
for(x=0; x<basecolorimg.height; x++){
var aov = ao[aoi++];
//aov = 1-aov; aov*=aov; aov=1-aov;
basecolorimg.buff[ci++]*=aov;
basecolorimg.buff[ci++]*=aov;
basecolorimg.buff[ci++]*=aov;
}
}
}
basecolorimg.saveAsPng(basecoloro);
normalimg.saveAsPng(normalo);
}
basecolorimg.onloadEnd=normalimg.onloadEnd=pbrinfoimg.onloadEnd=function(){
loadi++;
if(loadi>=3) onallend();
};
basecolorimg.load(basecolor);
normalimg.load(normal);
pbrinfoimg.load(pbrinfo);
}
function doMergeTexture(){
testpath = 'E:/layaair/layaair/publish/LayaAirPublish/samples/as/3d/bin/h5/threeDimen/models/dude/';
var tasks = JSON.parse(fs.readFileSync( testpath + 'mergeTexture.json','utf8'));
console.log(tasks);
tasks.forEach((v,i)=>{
mergetexture(testpath+v[0],
testpath+v[1],
testpath+v[2],
testpath+v[3],
testpath+v[4], false);
});
}
/**
* @param {string} file
* @return {Uint32Array}
*/
function getCh(file){
var cap = fs.readFileSync(file);
var rgb = new Uint8Array(cap.buffer);
var w=824; var h =997;
var buff = new Uint32Array(w*h);
var si=0;
var oi=0;
for( var y=0; y<h; y++){
for(var x=0; x<w; x++){
var r = rgb[si++];
var g = rgb[si++];
var b = rgb[si++];
var a = rgb[si++];
buff[oi++]=r*256+g;
}
}
return buff;
}
function testRaw(){
/*
var buff = new Uint32Array(100*100*3);
var i=0;
for( var y=0; y<100; y++){
for(var x=0; x<100; x++){
buff[i++]=(100*Math.sin(x/10));
buff[i++]=(100*Math.sin(x/10));
buff[i++]=(100*Math.sin(x/10));
}
}
fs.writeFileSync('d:/temp/env/tt.raw',Buffer(buff.buffer),'binary');
*/
var w=824; var h =997;
var buff = new Float32Array(w*h*4);
var rc = getCh('d:/temp/env/capr.raw');
var gc = getCh('d:/temp/env/capg.raw');
var bc = getCh('d:/temp/env/capb.raw');
var si=0;
var oi=0;
for( var y=0; y<h; y++){
for(var x=0; x<w; x++){
buff[oi++]=rc[si]/255;
buff[oi++]=gc[si]/255;
buff[oi++]=bc[si]/255;
buff[oi++]=1.0;
si++;
}
}
fs.writeFileSync('d:/temp/env/tt.raw',Buffer(buff.buffer),'binary');
}
function unpack(r,g){
return r*256+g;
}
function packfloat(v){
var iv = v*65536.0;
}
function testGamma(){
var buff = new Float32Array(500*100*3);
var i=0;
for( var y=0; y<100; y++){
for(var x=0; x<500; x++){
var v = x/500;
v=v*v;
buff[i++]=v;
buff[i++]=v;
buff[i++]=v;
}
}
new PanoramaEnv(buff,500,100).saveAsPng('d:/temp/g3.png');
}
//testGamma();
//testRaw();
//doMergeTexture();
//doHandleEnvTexture();
//var orgb = new Float32Array(3);
//spectral_color(470,orgb);
//console.log(orgb);
/**
* 功能:
* 1. 预处理环境贴图,生成一个mipmaps文件。
* 2. 预处理环境贴图,生成diffuse的球谐参数
* 3. 处理全景图转换成cubemap
* 4. 合并sp输出的符合UE的贴图。
* 5. 波长转RGB颜色
* 6. 法线贴图格式转换(d3d-opengl) normalmap_u3d_webgl
*
* 小功能
* testGamma
*/