UNPKG

pbrtools

Version:
1,293 lines (1,206 loc) 39.7 kB
//@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 */