war3-model-blp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE +5 -0
- package/README.md +88 -0
- package/dist/war3-model-blp.js +825 -0
- package/examples/browser.html +95 -0
- package/examples/node.cjs +18 -0
- package/package.json +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017-2023 4eb0da
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/NOTICE
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# war3-model-blp
|
|
2
|
+
|
|
3
|
+
Lightweight BLP functionality extracted from [war3-model](https://github.com/4eb0da/war3-model), focused on BLP image decoding without the full model viewer/runtime.
|
|
4
|
+
|
|
5
|
+
This package currently exposes the BLP1 decoder bundled from `war3-model` 4.0.1.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install war3-model-blp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Node.js
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
const fs = require("node:fs");
|
|
19
|
+
const { decodeBLP, getBLPImageData } = require("war3-model-blp");
|
|
20
|
+
|
|
21
|
+
const buffer = fs.readFileSync("ReplaceableTextures/CommandButtons/BTNFootman.blp");
|
|
22
|
+
const blp = decodeBLP(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength));
|
|
23
|
+
const imageData = getBLPImageData(blp, 0);
|
|
24
|
+
|
|
25
|
+
console.log(imageData.width, imageData.height, imageData.data);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Run the Node example with a local BLP file:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
node examples/node.cjs path/to/file.blp
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Browser
|
|
35
|
+
|
|
36
|
+
```html
|
|
37
|
+
<script src="dist/war3-model-blp.js"></script>
|
|
38
|
+
<script>
|
|
39
|
+
const { decodeBLP, getBLPImageData } = globalThis.war3modelBlp;
|
|
40
|
+
</script>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Browser demo
|
|
44
|
+
|
|
45
|
+
Open [examples/browser.html](examples/browser.html) in a browser, choose a `.blp` file, and the demo will decode mipmap 0 onto a canvas.
|
|
46
|
+
|
|
47
|
+
## Tests
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
npm test
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The automated test is Node-only and verifies the package entry plus a synthetic 1x1 red BLP1 fixture stored at [test/fixtures/red-1x1.blp](test/fixtures/red-1x1.blp). The browser test case is intentionally kept as [examples/browser.html](examples/browser.html), because it needs a real browser and a local `.blp` file selected by the user.
|
|
54
|
+
|
|
55
|
+
## API
|
|
56
|
+
|
|
57
|
+
### `decodeBLP(arrayBuffer)`
|
|
58
|
+
|
|
59
|
+
Parses a BLP1 image from an `ArrayBuffer` and returns decoded metadata plus mipmap information.
|
|
60
|
+
|
|
61
|
+
### `getBLPImageData(blp, mipmapIndex)`
|
|
62
|
+
|
|
63
|
+
Decodes one mipmap from the object returned by `decodeBLP`.
|
|
64
|
+
|
|
65
|
+
Returns an `ImageData` object when `ImageData` is available. In non-browser runtimes it returns an ImageData-like object:
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
{
|
|
69
|
+
width: number,
|
|
70
|
+
height: number,
|
|
71
|
+
data: Uint8ClampedArray,
|
|
72
|
+
colorSpace: "srgb"
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Scope
|
|
77
|
+
|
|
78
|
+
- BLP1 is supported.
|
|
79
|
+
- BLP0 and BLP2 are not supported by this extracted decoder.
|
|
80
|
+
- This package is intended as a small decoder bundle, not a full `war3-model` replacement.
|
|
81
|
+
|
|
82
|
+
## Attribution
|
|
83
|
+
|
|
84
|
+
The decoder is extracted from `war3-model` 4.0.1.
|
|
85
|
+
|
|
86
|
+
Source: <https://github.com/4eb0da/war3-model>
|
|
87
|
+
|
|
88
|
+
License: MIT. See [LICENSE](LICENSE) and [NOTICE](NOTICE).
|
|
@@ -0,0 +1,825 @@
|
|
|
1
|
+
(()=>{var ge=function(e){return e[e.INT1=0]="INT1",e[e.FLOAT1=1]="FLOAT1",e[e.FLOAT3=2]="FLOAT3",e[e.FLOAT4=3]="FLOAT4",e}(ge||{}),rr={[ge.INT1]:1,[ge.FLOAT1]:1,[ge.FLOAT3]:3,[ge.FLOAT4]:4};var pe=function(e){return e[e.INT1=0]="INT1",e[e.FLOAT1=1]="FLOAT1",e[e.FLOAT3=2]="FLOAT3",e[e.FLOAT4=3]="FLOAT4",e}(pe||{}),tr={[pe.INT1]:1,[pe.FLOAT1]:1,[pe.FLOAT3]:3,[pe.FLOAT4]:4};var me=function(e){return e[e.INT1=0]="INT1",e[e.FLOAT1=1]="FLOAT1",e[e.FLOAT3=2]="FLOAT3",e[e.FLOAT4=3]="FLOAT4",e}(me||{}),nr={[me.INT1]:1,[me.FLOAT1]:1,[me.FLOAT3]:3,[me.FLOAT4]:4};var De=function(e){return e[e.BLP0=0]="BLP0",e[e.BLP1=1]="BLP1",e[e.BLP2=2]="BLP2",e}({}),xe=function(e){return e[e.JPEG=0]="JPEG",e[e.Direct=1]="Direct",e}({}),Re=function(){"use strict";var r=new Int32Array([0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63]),a=4017,n=799,s=3406,g=2276,T=1567,b=3784,F=5793,k=2896;function I(){}function J(B,h){for(var t=0,E=[],x,i,c=16;c>0&&!B[c-1];)c--;E.push({children:[],index:0});var d=E[0],o;for(x=0;x<c;x++){for(i=0;i<B[x];i++){for(d=E.pop(),d.children[d.index]=h[t];d.index>0;)d=E.pop();for(d.index++,E.push(d);E.length<=x;)E.push(o={children:[],index:0}),d.children[d.index]=o.children,d=o;t++}x+1<c&&(E.push(o={children:[],index:0}),d.children[d.index]=o.children,d=o)}return E[0].children}function M(B,h,t){return 64*((B.blocksPerLine+1)*h+t)}function H(B,h,t,E,x,i,c,d,o){t.precision,t.samplesPerLine,t.scanLines;var S=t.mcusPerLine,y=t.progressive;t.maxH,t.maxV;var A=h,l=0,v=0;function u(){if(v>0)return v--,l>>v&1;if(l=B[h++],l==255){var m=B[h++];if(m)throw"unexpected marker: "+(l<<8|m).toString(16)}return v=7,l>>>7}function f(m){for(var P=m,L;(L=u())!==null;){if(P=P[L],typeof P=="number")return P;if(typeof P!="object")throw"invalid huffman sequence"}return null}function R(m){for(var P=0;m>0;){var L=u();if(L===null)return;P=P<<1|L,m--}return P}function p(m){var P=R(m);return P>=1<<m-1?P:P+(-1<<m)+1}function fe(m,P){var L=f(m.huffmanTableDC),U=L===0?0:p(L);m.blockData[P]=m.pred+=U;for(var C=1;C<64;){var D=f(m.huffmanTableAC),w=D&15,Y=D>>4;if(w===0){if(Y<15)break;C+=16;continue}C+=Y;var Se=r[C];m.blockData[P+Se]=p(w),C++}}function K(m,P){var L=f(m.huffmanTableDC),U=L===0?0:p(L)<<o;m.blockData[P]=m.pred+=U}function $(m,P){m.blockData[P]|=u()<<o}var _=0;function re(m,P){if(_>0){_--;return}for(var L=i,U=c;L<=U;){var C=f(m.huffmanTableAC),D=C&15,w=C>>4;if(D===0){if(w<15){_=R(w)+(1<<w)-1;break}L+=16;continue}L+=w;var Y=r[L];m.blockData[P+Y]=p(D)*(1<<o),L++}}var O=0,X;function ie(m,P){for(var L=i,U=c,C=0;L<=U;){var D=r[L];switch(O){case 0:var w=f(m.huffmanTableAC),Y=w&15,C=w>>4;if(Y===0)C<15?(_=R(C)+(1<<C),O=4):(C=16,O=1);else{if(Y!==1)throw"invalid ACn encoding";X=p(Y),O=C?2:3}continue;case 1:case 2:m.blockData[P+D]?m.blockData[P+D]+=u()<<o:(C--,C===0&&(O=O==2?3:0));break;case 3:m.blockData[P+D]?m.blockData[P+D]+=u()<<o:(m.blockData[P+D]=X<<o,O=0);break;case 4:m.blockData[P+D]&&(m.blockData[P+D]+=u()<<o);break}L++}O===4&&(_--,_===0&&(O=0))}function oe(m,P,L,U,C){var D=L/S|0,w=L%S;P(m,M(m,D*m.v+U,w*m.h+C))}function te(m,P,L){P(m,M(m,L/m.blocksPerLine|0,L%m.blocksPerLine))}var j=E.length,z,W,ae,Z,Q,se;y?i===0?se=d===0?K:$:se=d===0?re:ie:se=fe;var ne=0,V,ue;j==1?ue=E[0].blocksPerLine*E[0].blocksPerColumn:ue=S*t.mcusPerColumn,x||(x=ue);for(var de,be;ne<ue;){for(W=0;W<j;W++)E[W].pred=0;if(_=0,j==1)for(z=E[0],Q=0;Q<x;Q++)te(z,se,ne),ne++;else for(Q=0;Q<x;Q++){for(W=0;W<j;W++)for(z=E[W],de=z.h,be=z.v,ae=0;ae<be;ae++)for(Z=0;Z<de;Z++)oe(z,se,ne,ae,Z);ne++}if(v=0,V=B[h]<<8|B[h+1],V<=65280)throw"marker was not found";if(V>=65488&&V<=65495)h+=2;else break}return h-A}function ce(B,h,t){var E=B.quantizationTable,x,i,c,d,o,S,y,A,l,v;for(v=0;v<64;v++)t[v]=B.blockData[h+v]*E[v];for(v=0;v<8;++v){var u=8*v;if(t[1+u]==0&&t[2+u]==0&&t[3+u]==0&&t[4+u]==0&&t[5+u]==0&&t[6+u]==0&&t[7+u]==0){l=F*t[0+u]+512>>10,t[0+u]=l,t[1+u]=l,t[2+u]=l,t[3+u]=l,t[4+u]=l,t[5+u]=l,t[6+u]=l,t[7+u]=l;continue}x=F*t[0+u]+128>>8,i=F*t[4+u]+128>>8,c=t[2+u],d=t[6+u],o=k*(t[1+u]-t[7+u])+128>>8,A=k*(t[1+u]+t[7+u])+128>>8,S=t[3+u]<<4,y=t[5+u]<<4,l=x-i+1>>1,x=x+i+1>>1,i=l,l=c*b+d*T+128>>8,c=c*T-d*b+128>>8,d=l,l=o-y+1>>1,o=o+y+1>>1,y=l,l=A+S+1>>1,S=A-S+1>>1,A=l,l=x-d+1>>1,x=x+d+1>>1,d=l,l=i-c+1>>1,i=i+c+1>>1,c=l,l=o*g+A*s+2048>>12,o=o*s-A*g+2048>>12,A=l,l=S*n+y*a+2048>>12,S=S*a-y*n+2048>>12,y=l,t[0+u]=x+A,t[7+u]=x-A,t[1+u]=i+y,t[6+u]=i-y,t[2+u]=c+S,t[5+u]=c-S,t[3+u]=d+o,t[4+u]=d-o}for(v=0;v<8;++v){var f=v;if(t[8+f]==0&&t[16+f]==0&&t[24+f]==0&&t[32+f]==0&&t[40+f]==0&&t[48+f]==0&&t[56+f]==0){l=F*t[v+0]+8192>>14,t[0+f]=l,t[8+f]=l,t[16+f]=l,t[24+f]=l,t[32+f]=l,t[40+f]=l,t[48+f]=l,t[56+f]=l;continue}x=F*t[0+f]+2048>>12,i=F*t[32+f]+2048>>12,c=t[16+f],d=t[48+f],o=k*(t[8+f]-t[56+f])+2048>>12,A=k*(t[8+f]+t[56+f])+2048>>12,S=t[24+f],y=t[40+f],l=x-i+1>>1,x=x+i+1>>1,i=l,l=c*b+d*T+2048>>12,c=c*T-d*b+2048>>12,d=l,l=o-y+1>>1,o=o+y+1>>1,y=l,l=A+S+1>>1,S=A-S+1>>1,A=l,l=x-d+1>>1,x=x+d+1>>1,d=l,l=i-c+1>>1,i=i+c+1>>1,c=l,l=o*g+A*s+2048>>12,o=o*s-A*g+2048>>12,A=l,l=S*n+y*a+2048>>12,S=S*a-y*n+2048>>12,y=l,t[0+f]=x+A,t[56+f]=x-A,t[8+f]=i+y,t[48+f]=i-y,t[16+f]=c+S,t[40+f]=c-S,t[24+f]=d+o,t[32+f]=d-o}for(v=0;v<64;++v){var R=h+v,p=t[v];p=p<=-2056?0:p>=2024?255:p+2056>>4,B.blockData[R]=p}}function ee(B,h){var t=h.blocksPerLine,E=h.blocksPerColumn;t<<3;for(var x=new Int32Array(64),i=0;i<E;i++)for(var c=0;c<t;c++)ce(h,M(h,i,c),x);return h.blockData}function q(B){return B<=0?0:B>=255?255:B|0}return I.prototype={load:function(h){var t=new XMLHttpRequest;t.open("GET",h,!0),t.responseType="arraybuffer",t.onload=function(){var E=new Uint8Array(t.response||t.mozResponseArrayBuffer);this.parse(E),this.onload&&this.onload()}.bind(this),t.send(null)},loadFromBuffer:function(h){this.parse(h),this.onload&&this.onload()},parse:function(h){function t(){var U=h[i]<<8|h[i+1];return i+=2,U}function E(){var U=t(),C=h.subarray(i,i+U-2);return i+=C.length,C}function x(U){for(var C=Math.ceil(U.samplesPerLine/8/U.maxH),D=Math.ceil(U.scanLines/8/U.maxV),w=0;w<U.components.length;w++){V=U.components[w];var Y=Math.ceil(Math.ceil(U.samplesPerLine/8)*V.h/U.maxH),Se=Math.ceil(Math.ceil(U.scanLines/8)*V.v/U.maxV),Ve=C*V.h,Me=64*(D*V.v)*(Ve+1);V.blockData=new Int16Array(Me),V.blocksPerLine=Y,V.blocksPerColumn=Se}U.mcusPerLine=C,U.mcusPerColumn=D}var i=0;h.length;var c=null,d=null,o,S,y=[],A=[],l=[],v=t();if(v!=65496)throw"SOI not found";for(v=t();v!=65497;){var u,f,R;switch(v){case 65504:case 65505:case 65506:case 65507:case 65508:case 65509:case 65510:case 65511:case 65512:case 65513:case 65514:case 65515:case 65516:case 65517:case 65518:case 65519:case 65534:var p=E();v===65504&&p[0]===74&&p[1]===70&&p[2]===73&&p[3]===70&&p[4]===0&&(c={version:{major:p[5],minor:p[6]},densityUnits:p[7],xDensity:p[8]<<8|p[9],yDensity:p[10]<<8|p[11],thumbWidth:p[12],thumbHeight:p[13],thumbData:p.subarray(14,14+3*p[12]*p[13])}),v===65518&&p[0]===65&&p[1]===100&&p[2]===111&&p[3]===98&&p[4]===101&&p[5]===0&&(d={version:p[6],flags0:p[7]<<8|p[8],flags1:p[9]<<8|p[10],transformCode:p[11]});break;case 65499:for(var fe=t()+i-2;i<fe;){var K=h[i++],$=new Int32Array(64);if(K>>4===0)for(f=0;f<64;f++){var _=r[f];$[_]=h[i++]}else if(K>>4===1)for(f=0;f<64;f++){var _=r[f];$[_]=t()}else throw"DQT: invalid table spec";y[K&15]=$}break;case 65472:case 65473:case 65474:if(o)throw"Only single frame JPEGs supported";t(),o={},o.extended=v===65473,o.progressive=v===65474,o.precision=h[i++],o.scanLines=t(),o.samplesPerLine=t(),o.components=[],o.componentIds={};var re=h[i++],O,X=0,ie=0;for(u=0;u<re;u++){O=h[i];var oe=h[i+1]>>4,te=h[i+1]&15;X<oe&&(X=oe),ie<te&&(ie=te);var j=h[i+2],R=o.components.push({h:oe,v:te,quantizationTable:y[j]});o.componentIds[O]=R-1,i+=3}o.maxH=X,o.maxV=ie,x(o);break;case 65476:var z=t();for(u=2;u<z;){var W=h[i++],ae=new Uint8Array(16),Z=0;for(f=0;f<16;f++,i++)Z+=ae[f]=h[i];var Q=new Uint8Array(Z);for(f=0;f<Z;f++,i++)Q[f]=h[i];u+=17+Z,(W>>4===0?l:A)[W&15]=J(ae,Q)}break;case 65501:t(),S=t();break;case 65498:t();var se=h[i++],ne=[],V;for(u=0;u<se;u++){var ue=o.componentIds[h[i++]];V=o.components[ue];var de=h[i++];V.huffmanTableDC=l[de>>4],V.huffmanTableAC=A[de&15],ne.push(V)}var be=h[i++],m=h[i++],P=h[i++],L=H(h,i,o,ne,S,be,m,P>>4,P&15);i+=L;break;default:if(h[i-3]==255&&h[i-2]>=192&&h[i-2]<=254){i-=3;break}throw"unknown JPEG marker "+v.toString(16)}v=t()}this.width=o.samplesPerLine,this.height=o.scanLines,this.jfif=c,this.adobe=d,this.components=[];for(var u=0;u<o.components.length;u++){var V=o.components[u];this.components.push({output:ee(o,V),scaleX:V.h/o.maxH,scaleY:V.v/o.maxV,blocksPerLine:V.blocksPerLine,blocksPerColumn:V.blocksPerColumn})}},getData:function(h,t,E){var x=this.width/t,i=this.height/E,c,d,o,S,y,A,l=0,v=this.components.length;t*E*v;var u=h.data,f=new Uint8Array((this.components[0].blocksPerLine<<3)*this.components[0].blocksPerColumn*8);for(A=0;A<v;A++){c=this.components[A<3?2-A:A];for(var R=c.blocksPerLine,p=c.blocksPerColumn,fe=R<<3,K,$,_=0,re=0;re<p;re++)for(var O=re<<3,X=0;X<R;X++){var ie=M(c,re,X),l=0,oe=X<<3;for(K=0;K<8;K++){var _=(O+K)*fe;for($=0;$<8;$++)f[_+oe+$]=c.output[ie+l++]}}d=c.scaleX*x,o=c.scaleY*i,l=A;var te,j,z;for(y=0;y<E;y++)for(S=0;S<t;S++)j=0|y*o,te=0|S*d,z=j*fe+te,u[l]=f[z],l+=v}return u},copyToImageData:function(h){var t=h.width,E=h.height,x=t*E*4,i=h.data,c=this.getData(t,E),d=0,o=0,S,y,A,l,v,u,f,R,p;switch(this.components.length){case 1:for(;o<x;)A=c[d++],i[o++]=A,i[o++]=A,i[o++]=A,i[o++]=255;break;case 3:for(;o<x;)f=c[d++],R=c[d++],p=c[d++],i[o++]=f,i[o++]=R,i[o++]=p,i[o++]=255;break;case 4:for(;o<x;)v=c[d++],u=c[d++],A=c[d++],l=c[d++],S=255-l,y=S/255,f=q(S-v*y),R=q(S-u*y),p=q(S-A*y),i[o++]=f,i[o++]=R,i[o++]=p,i[o++]=255;break;default:throw"Unsupported color mode"}}},I}();function we(e){let r=new Re;r.loadFromBuffer(e);var a;return typeof ImageData<"u"?a=new ImageData(r.width,r.height):a={width:r.width,height:r.height,data:new Uint8ClampedArray(r.width*r.height*4)},r.getData(a,r.width,r.height),a}function Ge(e,r){return String.fromCharCode(e.getUint8(r),e.getUint8(r+1),e.getUint8(r+2),e.getUint8(r+3))}function le(e,r){return e.getUint32(r*4,!0)}function Ie(e,r,a){let n=e[Math.floor(a*r/8)],s=8/r;return n>>s-a%s-1&(1<<r)-1}function _e(e,r){return typeof ImageData<"u"?new ImageData(e,r):{width:e,height:r,data:new Uint8ClampedArray(e*r*4),colorSpace:"srgb"}}function Fe(e){let r=new DataView(e),a={type:De.BLP1,width:0,height:0,content:xe.JPEG,alphaBits:0,mipmaps:[],data:e},n=Ge(r,0);if(n==="BLP0"||n==="BLP2")throw new Error("BLP0/BLP2 not supported");if(n!=="BLP1")throw new Error("Not a blp image");if(a.content=le(r,1),a.content!==xe.JPEG&&a.content!==xe.Direct)throw new Error("Unknown BLP content");a.alphaBits=le(r,2),a.width=le(r,3),a.height=le(r,4);for(let s=0;s<16;++s){let g={offset:le(r,7+s),size:le(r,23+s)};if(g.size>0)a.mipmaps.push(g);else break}return a}function Ue(e,r){let a=new DataView(e.data),n=new Uint8Array(e.data),s=e.mipmaps[r];if(e.content===xe.JPEG){let g=le(a,39),T=new Uint8Array(g+s.size);return T.set(n.subarray(160,160+g)),T.set(n.subarray(s.offset,s.offset+s.size),g),we(T)}else{let g=new Uint8Array(e.data,156,1024),T=e.width/(1<<r),b=e.height/(1<<r),F=T*b,k=new Uint8Array(e.data,s.offset+F,Math.ceil(F*e.alphaBits/8)),I=_e(T,b),J=255/((1<<e.alphaBits)-1);for(let M=0;M<F;++M){let H=a.getUint8(s.offset+M)*4;I.data[M*4]=g[H+2],I.data[M*4+1]=g[H+1],I.data[M*4+2]=g[H],e.alphaBits>0?I.data[M*4+3]=Ie(k,e.alphaBits,M)*J:I.data[M*4+3]=255}return I}}var N=typeof Float32Array<"u"?Float32Array:Array;Math.PI/180;Math.hypot||(Math.hypot=function(){for(var e=0,r=arguments.length;r--;)e+=arguments[r]*arguments[r];return Math.sqrt(e)});function Le(){var e=new N(9);return N!=Float32Array&&(e[1]=0,e[2]=0,e[3]=0,e[5]=0,e[6]=0,e[7]=0),e[0]=1,e[4]=1,e[8]=1,e}function Ae(){var e=new N(16);return N!=Float32Array&&(e[1]=0,e[2]=0,e[3]=0,e[4]=0,e[6]=0,e[7]=0,e[8]=0,e[9]=0,e[11]=0,e[12]=0,e[13]=0,e[14]=0),e[0]=1,e[5]=1,e[10]=1,e[15]=1,e}function G(){var e=new N(3);return N!=Float32Array&&(e[0]=0,e[1]=0,e[2]=0),e}function Oe(e){var r=e[0],a=e[1],n=e[2];return Math.hypot(r,a,n)}function ve(e,r,a){var n=new N(3);return n[0]=e,n[1]=r,n[2]=a,n}function Ne(e,r){var a=r[0],n=r[1],s=r[2],g=a*a+n*n+s*s;return g>0&&(g=1/Math.sqrt(g)),e[0]=r[0]*g,e[1]=r[1]*g,e[2]=r[2]*g,e}function ke(e,r){return e[0]*r[0]+e[1]*r[1]+e[2]*r[2]}function ye(e,r,a){var n=r[0],s=r[1],g=r[2],T=a[0],b=a[1],F=a[2];return e[0]=s*F-g*b,e[1]=g*T-n*F,e[2]=n*b-s*T,e}var He=Oe;(function(){var e=G();return function(r,a,n,s,g,T){var b,F;for(a||(a=3),n||(n=0),s?F=Math.min(s*a+n,r.length):F=r.length,b=n;b<F;b+=a)e[0]=r[b],e[1]=r[b+1],e[2]=r[b+2],g(e,e,T),r[b]=e[0],r[b+1]=e[1],r[b+2]=e[2];return r}})();function Pe(){var e=new N(4);return N!=Float32Array&&(e[0]=0,e[1]=0,e[2]=0,e[3]=0),e}function Xe(e,r,a,n){var s=new N(4);return s[0]=e,s[1]=r,s[2]=a,s[3]=n,s}function ze(e,r){var a=r[0],n=r[1],s=r[2],g=r[3],T=a*a+n*n+s*s+g*g;return T>0&&(T=1/Math.sqrt(T)),e[0]=a*T,e[1]=n*T,e[2]=s*T,e[3]=g*T,e}(function(){var e=Pe();return function(r,a,n,s,g,T){var b,F;for(a||(a=4),n||(n=0),s?F=Math.min(s*a+n,r.length):F=r.length,b=n;b<F;b+=a)e[0]=r[b],e[1]=r[b+1],e[2]=r[b+2],e[3]=r[b+3],g(e,e,T),r[b]=e[0],r[b+1]=e[1],r[b+2]=e[2],r[b+3]=e[3];return r}})();function Te(){var e=new N(4);return N!=Float32Array&&(e[0]=0,e[1]=0,e[2]=0),e[3]=1,e}function We(e,r,a){a=a*.5;var n=Math.sin(a);return e[0]=n*r[0],e[1]=n*r[1],e[2]=n*r[2],e[3]=Math.cos(a),e}function Ee(e,r,a,n){var s=r[0],g=r[1],T=r[2],b=r[3],F=a[0],k=a[1],I=a[2],J=a[3],M,H=s*F+g*k+T*I+b*J,ce,ee,q;return H<0&&(H=-H,F=-F,k=-k,I=-I,J=-J),1-H>1e-6?(M=Math.acos(H),ce=Math.sin(M),ee=Math.sin((1-n)*M)/ce,q=Math.sin(n*M)/ce):(ee=1-n,q=n),e[0]=ee*s+q*F,e[1]=ee*g+q*k,e[2]=ee*T+q*I,e[3]=ee*b+q*J,e}function qe(e,r){var a=r[0]+r[4]+r[8],n;if(a>0)n=Math.sqrt(a+1),e[3]=.5*n,n=.5/n,e[0]=(r[5]-r[7])*n,e[1]=(r[6]-r[2])*n,e[2]=(r[1]-r[3])*n;else{var s=0;r[4]>r[0]&&(s=1),r[8]>r[s*3+s]&&(s=2);var g=(s+1)%3,T=(s+2)%3;n=Math.sqrt(r[s*3+s]-r[g*3+g]-r[T*3+T]+1),e[s]=.5*n,n=.5/n,e[3]=(r[g*3+T]-r[T*3+g])*n,e[g]=(r[g*3+s]+r[s*3+g])*n,e[T]=(r[T*3+s]+r[s*3+T])*n}return e}var Ke=Xe;var Ce=ze,ir=function(){var e=G(),r=ve(1,0,0),a=ve(0,1,0);return function(n,s,g){var T=ke(s,g);return T<-.999999?(ye(e,r,s),He(e)<1e-6&&ye(e,a,s),Ne(e,e),We(n,e,Math.PI),n):T>.999999?(n[0]=0,n[1]=0,n[2]=0,n[3]=1,n):(ye(e,s,g),n[0]=e[0],n[1]=e[1],n[2]=e[2],n[3]=1+T,Ce(n,n))}}(),or=function(){var e=Te(),r=Te();return function(a,n,s,g,T,b){return Ee(e,n,T,b),Ee(r,s,g,b),Ee(a,e,r,2*b*(1-b)),a}}();(function(){var e=Le();return function(r,a,n,s){return e[0]=n[0],e[3]=n[1],e[6]=n[2],e[1]=s[0],e[4]=s[1],e[7]=s[2],e[2]=-a[0],e[5]=-a[1],e[8]=-a[2],Ce(r,qe(r,e))}})();var ar=ve(0,0,0),sr=Pe(),lr=Pe(),fr=Pe(),ur=G(),hr=G();var $e=`attribute vec3 aVertexPosition;
|
|
2
|
+
attribute vec3 aNormal;
|
|
3
|
+
attribute vec2 aTextureCoord;
|
|
4
|
+
attribute vec4 aGroup;
|
|
5
|
+
|
|
6
|
+
uniform mat4 uMVMatrix;
|
|
7
|
+
uniform mat4 uPMatrix;
|
|
8
|
+
uniform mat4 uNodesMatrices[\${MAX_NODES}];
|
|
9
|
+
|
|
10
|
+
varying vec3 vNormal;
|
|
11
|
+
varying vec2 vTextureCoord;
|
|
12
|
+
|
|
13
|
+
void main(void) {
|
|
14
|
+
vec4 position = vec4(aVertexPosition, 1.0);
|
|
15
|
+
int count = 1;
|
|
16
|
+
vec4 sum = uNodesMatrices[int(aGroup[0])] * position;
|
|
17
|
+
|
|
18
|
+
if (aGroup[1] < \${MAX_NODES}.) {
|
|
19
|
+
sum += uNodesMatrices[int(aGroup[1])] * position;
|
|
20
|
+
count += 1;
|
|
21
|
+
}
|
|
22
|
+
if (aGroup[2] < \${MAX_NODES}.) {
|
|
23
|
+
sum += uNodesMatrices[int(aGroup[2])] * position;
|
|
24
|
+
count += 1;
|
|
25
|
+
}
|
|
26
|
+
if (aGroup[3] < \${MAX_NODES}.) {
|
|
27
|
+
sum += uNodesMatrices[int(aGroup[3])] * position;
|
|
28
|
+
count += 1;
|
|
29
|
+
}
|
|
30
|
+
sum.xyz /= float(count);
|
|
31
|
+
sum.w = 1.;
|
|
32
|
+
position = sum;
|
|
33
|
+
|
|
34
|
+
gl_Position = uPMatrix * uMVMatrix * position;
|
|
35
|
+
vTextureCoord = aTextureCoord;
|
|
36
|
+
vNormal = aNormal;
|
|
37
|
+
}`;var Ye=`attribute vec3 aVertexPosition;
|
|
38
|
+
attribute vec3 aNormal;
|
|
39
|
+
attribute vec2 aTextureCoord;
|
|
40
|
+
attribute vec4 aSkin;
|
|
41
|
+
attribute vec4 aBoneWeight;
|
|
42
|
+
attribute vec4 aTangent;
|
|
43
|
+
|
|
44
|
+
uniform mat4 uMVMatrix;
|
|
45
|
+
uniform mat4 uPMatrix;
|
|
46
|
+
uniform mat4 uNodesMatrices[\${MAX_NODES}];
|
|
47
|
+
|
|
48
|
+
varying vec3 vNormal;
|
|
49
|
+
varying vec3 vTangent;
|
|
50
|
+
varying vec3 vBinormal;
|
|
51
|
+
varying vec2 vTextureCoord;
|
|
52
|
+
varying mat3 vTBN;
|
|
53
|
+
varying vec3 vFragPos;
|
|
54
|
+
|
|
55
|
+
void main(void) {
|
|
56
|
+
vec4 position = vec4(aVertexPosition, 1.0);
|
|
57
|
+
mat4 sum;
|
|
58
|
+
|
|
59
|
+
// sum += uNodesMatrices[int(aSkin[0])] * 1.;
|
|
60
|
+
sum += uNodesMatrices[int(aSkin[0])] * aBoneWeight[0];
|
|
61
|
+
sum += uNodesMatrices[int(aSkin[1])] * aBoneWeight[1];
|
|
62
|
+
sum += uNodesMatrices[int(aSkin[2])] * aBoneWeight[2];
|
|
63
|
+
sum += uNodesMatrices[int(aSkin[3])] * aBoneWeight[3];
|
|
64
|
+
|
|
65
|
+
mat3 rotation = mat3(sum);
|
|
66
|
+
|
|
67
|
+
position = sum * position;
|
|
68
|
+
position.w = 1.;
|
|
69
|
+
|
|
70
|
+
gl_Position = uPMatrix * uMVMatrix * position;
|
|
71
|
+
vTextureCoord = aTextureCoord;
|
|
72
|
+
|
|
73
|
+
vec3 normal = aNormal;
|
|
74
|
+
vec3 tangent = aTangent.xyz;
|
|
75
|
+
|
|
76
|
+
// https://learnopengl.com/Advanced-Lighting/Normal-Mapping
|
|
77
|
+
tangent = normalize(tangent - dot(tangent, normal) * normal);
|
|
78
|
+
|
|
79
|
+
vec3 binormal = cross(normal, tangent) * aTangent.w;
|
|
80
|
+
|
|
81
|
+
normal = normalize(rotation * normal);
|
|
82
|
+
tangent = normalize(rotation * tangent);
|
|
83
|
+
binormal = normalize(rotation * binormal);
|
|
84
|
+
|
|
85
|
+
vNormal = normal;
|
|
86
|
+
vTangent = tangent;
|
|
87
|
+
vBinormal = binormal;
|
|
88
|
+
|
|
89
|
+
vTBN = mat3(tangent, binormal, normal);
|
|
90
|
+
|
|
91
|
+
vFragPos = position.xyz;
|
|
92
|
+
}`,je=`#version 300 es
|
|
93
|
+
in vec3 aVertexPosition;
|
|
94
|
+
in vec3 aNormal;
|
|
95
|
+
in vec2 aTextureCoord;
|
|
96
|
+
in vec4 aSkin;
|
|
97
|
+
in vec4 aBoneWeight;
|
|
98
|
+
in vec4 aTangent;
|
|
99
|
+
|
|
100
|
+
uniform mat4 uMVMatrix;
|
|
101
|
+
uniform mat4 uPMatrix;
|
|
102
|
+
uniform mat4 uNodesMatrices[\${MAX_NODES}];
|
|
103
|
+
|
|
104
|
+
out vec3 vNormal;
|
|
105
|
+
out vec3 vTangent;
|
|
106
|
+
out vec3 vBinormal;
|
|
107
|
+
out vec2 vTextureCoord;
|
|
108
|
+
out mat3 vTBN;
|
|
109
|
+
out vec3 vFragPos;
|
|
110
|
+
|
|
111
|
+
void main(void) {
|
|
112
|
+
vec4 position = vec4(aVertexPosition, 1.0);
|
|
113
|
+
mat4 sum;
|
|
114
|
+
|
|
115
|
+
// sum += uNodesMatrices[int(aSkin[0])] * 1.;
|
|
116
|
+
sum += uNodesMatrices[int(aSkin[0])] * aBoneWeight[0];
|
|
117
|
+
sum += uNodesMatrices[int(aSkin[1])] * aBoneWeight[1];
|
|
118
|
+
sum += uNodesMatrices[int(aSkin[2])] * aBoneWeight[2];
|
|
119
|
+
sum += uNodesMatrices[int(aSkin[3])] * aBoneWeight[3];
|
|
120
|
+
|
|
121
|
+
mat3 rotation = mat3(sum);
|
|
122
|
+
|
|
123
|
+
position = sum * position;
|
|
124
|
+
position.w = 1.;
|
|
125
|
+
|
|
126
|
+
gl_Position = uPMatrix * uMVMatrix * position;
|
|
127
|
+
vTextureCoord = aTextureCoord;
|
|
128
|
+
|
|
129
|
+
vec3 normal = aNormal;
|
|
130
|
+
vec3 tangent = aTangent.xyz;
|
|
131
|
+
|
|
132
|
+
// https://learnopengl.com/Advanced-Lighting/Normal-Mapping
|
|
133
|
+
tangent = normalize(tangent - dot(tangent, normal) * normal);
|
|
134
|
+
|
|
135
|
+
vec3 binormal = cross(normal, tangent) * aTangent.w;
|
|
136
|
+
|
|
137
|
+
normal = normalize(rotation * normal);
|
|
138
|
+
tangent = normalize(rotation * tangent);
|
|
139
|
+
binormal = normalize(rotation * binormal);
|
|
140
|
+
|
|
141
|
+
vNormal = normal;
|
|
142
|
+
vTangent = tangent;
|
|
143
|
+
vBinormal = binormal;
|
|
144
|
+
|
|
145
|
+
vTBN = mat3(tangent, binormal, normal);
|
|
146
|
+
|
|
147
|
+
vFragPos = position.xyz;
|
|
148
|
+
}`;var Ze=`#version 300 es
|
|
149
|
+
precision mediump float;
|
|
150
|
+
|
|
151
|
+
in vec2 vTextureCoord;
|
|
152
|
+
in vec3 vNormal;
|
|
153
|
+
in vec3 vTangent;
|
|
154
|
+
in vec3 vBinormal;
|
|
155
|
+
in mat3 vTBN;
|
|
156
|
+
in vec3 vFragPos;
|
|
157
|
+
|
|
158
|
+
out vec4 FragColor;
|
|
159
|
+
|
|
160
|
+
uniform sampler2D uSampler;
|
|
161
|
+
uniform sampler2D uNormalSampler;
|
|
162
|
+
uniform sampler2D uOrmSampler;
|
|
163
|
+
uniform vec3 uReplaceableColor;
|
|
164
|
+
uniform float uDiscardAlphaLevel;
|
|
165
|
+
uniform mat3 uTVertexAnim;
|
|
166
|
+
uniform vec3 uLightPos;
|
|
167
|
+
uniform vec3 uLightColor;
|
|
168
|
+
uniform vec3 uCameraPos;
|
|
169
|
+
uniform vec3 uShadowParams;
|
|
170
|
+
uniform sampler2D uShadowMapSampler;
|
|
171
|
+
uniform mat4 uShadowMapLightMatrix;
|
|
172
|
+
uniform bool uHasEnv;
|
|
173
|
+
uniform samplerCube uIrradianceMap;
|
|
174
|
+
uniform samplerCube uPrefilteredEnv;
|
|
175
|
+
uniform sampler2D uBRDFLUT;
|
|
176
|
+
uniform float uWireframe;
|
|
177
|
+
|
|
178
|
+
const float PI = 3.14159265359;
|
|
179
|
+
const float gamma = 2.2;
|
|
180
|
+
const float MAX_REFLECTION_LOD = \${MAX_ENV_MIP_LEVELS};
|
|
181
|
+
|
|
182
|
+
float distributionGGX(vec3 normal, vec3 halfWay, float roughness) {
|
|
183
|
+
float a = roughness * roughness;
|
|
184
|
+
float a2 = a * a;
|
|
185
|
+
float nDotH = max(dot(normal, halfWay), 0.0);
|
|
186
|
+
float nDotH2 = nDotH * nDotH;
|
|
187
|
+
|
|
188
|
+
float num = a2;
|
|
189
|
+
float denom = (nDotH2 * (a2 - 1.0) + 1.0);
|
|
190
|
+
denom = PI * denom * denom;
|
|
191
|
+
|
|
192
|
+
return num / denom;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
float geometrySchlickGGX(float nDotV, float roughness) {
|
|
196
|
+
float r = roughness + 1.;
|
|
197
|
+
float k = r * r / 8.;
|
|
198
|
+
// float k = roughness * roughness / 2.;
|
|
199
|
+
|
|
200
|
+
float num = nDotV;
|
|
201
|
+
float denom = nDotV * (1. - k) + k;
|
|
202
|
+
|
|
203
|
+
return num / denom;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
float geometrySmith(vec3 normal, vec3 viewDir, vec3 lightDir, float roughness) {
|
|
207
|
+
float nDotV = max(dot(normal, viewDir), .0);
|
|
208
|
+
float nDotL = max(dot(normal, lightDir), .0);
|
|
209
|
+
float ggx2 = geometrySchlickGGX(nDotV, roughness);
|
|
210
|
+
float ggx1 = geometrySchlickGGX(nDotL, roughness);
|
|
211
|
+
|
|
212
|
+
return ggx1 * ggx2;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
vec3 fresnelSchlick(float lightFactor, vec3 f0) {
|
|
216
|
+
return f0 + (1. - f0) * pow(clamp(1. - lightFactor, 0., 1.), 5.);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
vec3 fresnelSchlickRoughness(float lightFactor, vec3 f0, float roughness) {
|
|
220
|
+
return f0 + (max(vec3(1.0 - roughness), f0) - f0) * pow(clamp(1.0 - lightFactor, 0.0, 1.0), 5.0);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
void main(void) {
|
|
224
|
+
if (uWireframe > 0.) {
|
|
225
|
+
FragColor = vec4(1.);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
vec2 texCoord = (uTVertexAnim * vec3(vTextureCoord.s, vTextureCoord.t, 1.)).st;
|
|
230
|
+
|
|
231
|
+
vec4 orm = texture(uOrmSampler, texCoord);
|
|
232
|
+
|
|
233
|
+
float occlusion = orm.r;
|
|
234
|
+
float roughness = orm.g;
|
|
235
|
+
float metallic = orm.b;
|
|
236
|
+
float teamColorFactor = orm.a;
|
|
237
|
+
|
|
238
|
+
vec4 baseColor = texture(uSampler, texCoord);
|
|
239
|
+
vec3 teamColor = baseColor.rgb * uReplaceableColor;
|
|
240
|
+
baseColor.rgb = mix(baseColor.rgb, teamColor, teamColorFactor);
|
|
241
|
+
baseColor.rgb = pow(baseColor.rgb, vec3(gamma));
|
|
242
|
+
|
|
243
|
+
vec3 normal = texture(uNormalSampler, texCoord).rgb;
|
|
244
|
+
normal = normal * 2.0 - 1.0;
|
|
245
|
+
normal.x = -normal.x;
|
|
246
|
+
normal.y = -normal.y;
|
|
247
|
+
if (!gl_FrontFacing) {
|
|
248
|
+
normal = -normal;
|
|
249
|
+
}
|
|
250
|
+
normal = normalize(vTBN * -normal);
|
|
251
|
+
|
|
252
|
+
vec3 viewDir = normalize(uCameraPos - vFragPos);
|
|
253
|
+
vec3 reflected = reflect(-viewDir, normal);
|
|
254
|
+
|
|
255
|
+
vec3 lightDir = normalize(uLightPos - vFragPos);
|
|
256
|
+
float lightFactor = max(dot(normal, lightDir), .0);
|
|
257
|
+
vec3 radiance = uLightColor;
|
|
258
|
+
|
|
259
|
+
vec3 f0 = vec3(.04);
|
|
260
|
+
f0 = mix(f0, baseColor.rgb, metallic);
|
|
261
|
+
|
|
262
|
+
vec3 totalLight = vec3(0.);
|
|
263
|
+
vec3 halfWay = normalize(viewDir + lightDir);
|
|
264
|
+
float ndf = distributionGGX(normal, halfWay, roughness);
|
|
265
|
+
float g = geometrySmith(normal, viewDir, lightDir, roughness);
|
|
266
|
+
vec3 f = fresnelSchlick(max(dot(halfWay, viewDir), 0.), f0);
|
|
267
|
+
|
|
268
|
+
vec3 kS = f;
|
|
269
|
+
vec3 kD = vec3(1.);// - kS;
|
|
270
|
+
if (uHasEnv) {
|
|
271
|
+
kD *= 1.0 - metallic;
|
|
272
|
+
}
|
|
273
|
+
vec3 num = ndf * g * f;
|
|
274
|
+
float denom = 4. * max(dot(normal, viewDir), 0.) * max(dot(normal, lightDir), 0.) + .0001;
|
|
275
|
+
vec3 specular = num / denom;
|
|
276
|
+
|
|
277
|
+
totalLight = (kD * baseColor.rgb / PI + specular) * radiance * lightFactor;
|
|
278
|
+
|
|
279
|
+
if (uShadowParams[0] > .5) {
|
|
280
|
+
float shadowBias = uShadowParams[1];
|
|
281
|
+
float shadowStep = uShadowParams[2];
|
|
282
|
+
vec4 fragInLightPos = uShadowMapLightMatrix * vec4(vFragPos, 1.);
|
|
283
|
+
vec3 shadowMapCoord = fragInLightPos.xyz / fragInLightPos.w;
|
|
284
|
+
shadowMapCoord.xyz = (shadowMapCoord.xyz + 1.0) * .5;
|
|
285
|
+
|
|
286
|
+
int passes = 5;
|
|
287
|
+
float step = 1. / float(passes);
|
|
288
|
+
|
|
289
|
+
float lightDepth = texture(uShadowMapSampler, shadowMapCoord.xy).r;
|
|
290
|
+
float lightDepth0 = texture(uShadowMapSampler, vec2(shadowMapCoord.x + shadowStep, shadowMapCoord.y)).r;
|
|
291
|
+
float lightDepth1 = texture(uShadowMapSampler, vec2(shadowMapCoord.x, shadowMapCoord.y + shadowStep)).r;
|
|
292
|
+
float lightDepth2 = texture(uShadowMapSampler, vec2(shadowMapCoord.x, shadowMapCoord.y - shadowStep)).r;
|
|
293
|
+
float lightDepth3 = texture(uShadowMapSampler, vec2(shadowMapCoord.x - shadowStep, shadowMapCoord.y)).r;
|
|
294
|
+
float currentDepth = shadowMapCoord.z;
|
|
295
|
+
|
|
296
|
+
float visibility = 0.;
|
|
297
|
+
if (lightDepth > currentDepth - shadowBias) {
|
|
298
|
+
visibility += step;
|
|
299
|
+
}
|
|
300
|
+
if (lightDepth0 > currentDepth - shadowBias) {
|
|
301
|
+
visibility += step;
|
|
302
|
+
}
|
|
303
|
+
if (lightDepth1 > currentDepth - shadowBias) {
|
|
304
|
+
visibility += step;
|
|
305
|
+
}
|
|
306
|
+
if (lightDepth2 > currentDepth - shadowBias) {
|
|
307
|
+
visibility += step;
|
|
308
|
+
}
|
|
309
|
+
if (lightDepth3 > currentDepth - shadowBias) {
|
|
310
|
+
visibility += step;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
totalLight *= visibility;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
vec3 color;
|
|
317
|
+
|
|
318
|
+
if (uHasEnv) {
|
|
319
|
+
vec3 f = fresnelSchlickRoughness(max(dot(normal, viewDir), 0.0), f0, roughness);
|
|
320
|
+
vec3 kS = f;
|
|
321
|
+
vec3 kD = vec3(1.0) - kS;
|
|
322
|
+
kD *= 1.0 - metallic;
|
|
323
|
+
|
|
324
|
+
vec3 diffuse = texture(uIrradianceMap, normal).rgb * baseColor.rgb;
|
|
325
|
+
vec3 prefilteredColor = textureLod(uPrefilteredEnv, reflected, roughness * MAX_REFLECTION_LOD).rgb;
|
|
326
|
+
vec2 envBRDF = texture(uBRDFLUT, vec2(max(dot(normal, viewDir), 0.0), roughness)).rg;
|
|
327
|
+
specular = prefilteredColor * (f * envBRDF.x + envBRDF.y);
|
|
328
|
+
|
|
329
|
+
vec3 ambient = (kD * diffuse + specular) * occlusion;
|
|
330
|
+
color = ambient + totalLight;
|
|
331
|
+
} else {
|
|
332
|
+
vec3 ambient = vec3(.03);
|
|
333
|
+
ambient *= baseColor.rgb * occlusion;
|
|
334
|
+
color = ambient + totalLight;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
color = color / (vec3(1.) + color);
|
|
338
|
+
color = pow(color, vec3(1. / gamma));
|
|
339
|
+
|
|
340
|
+
FragColor = vec4(color, baseColor.a);
|
|
341
|
+
|
|
342
|
+
// hand-made alpha-test
|
|
343
|
+
if (FragColor[3] < uDiscardAlphaLevel) {
|
|
344
|
+
discard;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
`;var Qe=`struct VSUniforms {
|
|
348
|
+
mvMatrix: mat4x4f,
|
|
349
|
+
pMatrix: mat4x4f,
|
|
350
|
+
nodesMatrices: array<mat4x4f, \${MAX_NODES}>,
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
struct FSUniforms {
|
|
354
|
+
replaceableColor: vec3f,
|
|
355
|
+
replaceableType: u32,
|
|
356
|
+
discardAlphaLevel: f32,
|
|
357
|
+
wireframe: u32,
|
|
358
|
+
tVertexAnim: mat3x3f,
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
@group(0) @binding(0) var<uniform> vsUniforms: VSUniforms;
|
|
362
|
+
@group(1) @binding(0) var<uniform> fsUniforms: FSUniforms;
|
|
363
|
+
@group(1) @binding(1) var fsUniformSampler: sampler;
|
|
364
|
+
@group(1) @binding(2) var fsUniformTexture: texture_2d<f32>;
|
|
365
|
+
|
|
366
|
+
struct VSIn {
|
|
367
|
+
@location(0) vertexPosition: vec3f,
|
|
368
|
+
@location(1) normal: vec3f,
|
|
369
|
+
@location(2) textureCoord: vec2f,
|
|
370
|
+
@location(3) group: vec4<u32>,
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
struct VSOut {
|
|
374
|
+
@builtin(position) position: vec4f,
|
|
375
|
+
@location(0) normal: vec3f,
|
|
376
|
+
@location(1) textureCoord: vec2f,
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
@vertex fn vs(
|
|
380
|
+
in: VSIn
|
|
381
|
+
) -> VSOut {
|
|
382
|
+
var position: vec4f = vec4f(in.vertexPosition, 1.0);
|
|
383
|
+
var count: i32 = 1;
|
|
384
|
+
var sum: vec4f = vsUniforms.nodesMatrices[in.group[0]] * position;
|
|
385
|
+
|
|
386
|
+
if (in.group[1] < \${MAX_NODES}) {
|
|
387
|
+
sum += vsUniforms.nodesMatrices[in.group[1]] * position;
|
|
388
|
+
count += 1;
|
|
389
|
+
}
|
|
390
|
+
if (in.group[2] < \${MAX_NODES}) {
|
|
391
|
+
sum += vsUniforms.nodesMatrices[in.group[2]] * position;
|
|
392
|
+
count += 1;
|
|
393
|
+
}
|
|
394
|
+
if (in.group[3] < \${MAX_NODES}) {
|
|
395
|
+
sum += vsUniforms.nodesMatrices[in.group[3]] * position;
|
|
396
|
+
count += 1;
|
|
397
|
+
}
|
|
398
|
+
sum /= f32(count);
|
|
399
|
+
sum.w = 1.;
|
|
400
|
+
position = sum;
|
|
401
|
+
|
|
402
|
+
var out: VSOut;
|
|
403
|
+
out.position = vsUniforms.pMatrix * vsUniforms.mvMatrix * position;
|
|
404
|
+
out.textureCoord = in.textureCoord;
|
|
405
|
+
out.normal = in.normal;
|
|
406
|
+
return out;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
fn hypot(z: vec2f) -> f32 {
|
|
410
|
+
var t: f32 = 0;
|
|
411
|
+
var x: f32 = abs(z.x);
|
|
412
|
+
let y: f32 = abs(z.y);
|
|
413
|
+
t = min(x, y);
|
|
414
|
+
x = max(x, y);
|
|
415
|
+
t = t / x;
|
|
416
|
+
if (z.x == 0.0 && z.y == 0.0) {
|
|
417
|
+
return 0.0;
|
|
418
|
+
}
|
|
419
|
+
return x * sqrt(1.0 + t * t);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
@fragment fn fs(
|
|
423
|
+
in: VSOut
|
|
424
|
+
) -> @location(0) vec4f {
|
|
425
|
+
if (fsUniforms.wireframe > 0) {
|
|
426
|
+
return vec4f(1);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
let texCoord: vec2f = (fsUniforms.tVertexAnim * vec3f(in.textureCoord.x, in.textureCoord.y, 1.)).xy;
|
|
430
|
+
var color: vec4f = vec4f(0.0);
|
|
431
|
+
|
|
432
|
+
if (fsUniforms.replaceableType == 0) {
|
|
433
|
+
color = textureSample(fsUniformTexture, fsUniformSampler, texCoord);
|
|
434
|
+
} else if (fsUniforms.replaceableType == 1) {
|
|
435
|
+
color = vec4f(fsUniforms.replaceableColor, 1.0);
|
|
436
|
+
} else if (fsUniforms.replaceableType == 2) {
|
|
437
|
+
let dist: f32 = hypot(texCoord - vec2(0.5, 0.5)) * 2.;
|
|
438
|
+
let truncateDist: f32 = clamp(1. - dist * 1.4, 0., 1.);
|
|
439
|
+
let alpha: f32 = sin(truncateDist);
|
|
440
|
+
color = vec4f(fsUniforms.replaceableColor * alpha, 1.0);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// hand-made alpha-test
|
|
444
|
+
if (color.a < fsUniforms.discardAlphaLevel) {
|
|
445
|
+
discard;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return color;
|
|
449
|
+
}
|
|
450
|
+
`,Je=`struct VSUniforms {
|
|
451
|
+
mvMatrix: mat4x4f,
|
|
452
|
+
pMatrix: mat4x4f,
|
|
453
|
+
nodesMatrices: array<mat4x4f, \${MAX_NODES}>,
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
struct FSUniforms {
|
|
457
|
+
replaceableColor: vec3f,
|
|
458
|
+
// replaceableType: u32,
|
|
459
|
+
discardAlphaLevel: f32,
|
|
460
|
+
tVertexAnim: mat3x3f,
|
|
461
|
+
lightPos: vec3f,
|
|
462
|
+
hasEnv: u32,
|
|
463
|
+
lightColor: vec3f,
|
|
464
|
+
wireframe: u32,
|
|
465
|
+
cameraPos: vec3f,
|
|
466
|
+
shadowParams: vec3f,
|
|
467
|
+
shadowMapLightMatrix: mat4x4f,
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
@group(0) @binding(0) var<uniform> vsUniforms: VSUniforms;
|
|
471
|
+
@group(1) @binding(0) var<uniform> fsUniforms: FSUniforms;
|
|
472
|
+
@group(1) @binding(1) var fsUniformDiffuseSampler: sampler;
|
|
473
|
+
@group(1) @binding(2) var fsUniformDiffuseTexture: texture_2d<f32>;
|
|
474
|
+
@group(1) @binding(3) var fsUniformNormalSampler: sampler;
|
|
475
|
+
@group(1) @binding(4) var fsUniformNormalTexture: texture_2d<f32>;
|
|
476
|
+
@group(1) @binding(5) var fsUniformOrmSampler: sampler;
|
|
477
|
+
@group(1) @binding(6) var fsUniformOrmTexture: texture_2d<f32>;
|
|
478
|
+
@group(1) @binding(7) var fsUniformShadowSampler: sampler_comparison;
|
|
479
|
+
@group(1) @binding(8) var fsUniformShadowTexture: texture_depth_2d;
|
|
480
|
+
@group(1) @binding(9) var irradienceMapSampler: sampler;
|
|
481
|
+
@group(1) @binding(10) var irradienceMapTexture: texture_cube<f32>;
|
|
482
|
+
@group(1) @binding(11) var prefilteredEnvSampler: sampler;
|
|
483
|
+
@group(1) @binding(12) var prefilteredEnvTexture: texture_cube<f32>;
|
|
484
|
+
@group(1) @binding(13) var brdfLutSampler: sampler;
|
|
485
|
+
@group(1) @binding(14) var brdfLutTexture: texture_2d<f32>;
|
|
486
|
+
|
|
487
|
+
struct VSIn {
|
|
488
|
+
@location(0) vertexPosition: vec3f,
|
|
489
|
+
@location(1) normal: vec3f,
|
|
490
|
+
@location(2) textureCoord: vec2f,
|
|
491
|
+
@location(3) tangent: vec4f,
|
|
492
|
+
@location(4) skin: vec4<u32>,
|
|
493
|
+
@location(5) boneWeight: vec4f,
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
struct VSOut {
|
|
497
|
+
@builtin(position) position: vec4f,
|
|
498
|
+
@location(0) normal: vec3f,
|
|
499
|
+
@location(1) textureCoord: vec2f,
|
|
500
|
+
@location(2) tangent: vec3f,
|
|
501
|
+
@location(3) binormal: vec3f,
|
|
502
|
+
@location(4) fragPos: vec3f,
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
@vertex fn vs(
|
|
506
|
+
in: VSIn
|
|
507
|
+
) -> VSOut {
|
|
508
|
+
var position: vec4f = vec4f(in.vertexPosition, 1.0);
|
|
509
|
+
var sum: mat4x4f;
|
|
510
|
+
|
|
511
|
+
sum += vsUniforms.nodesMatrices[in.skin[0]] * in.boneWeight[0];
|
|
512
|
+
sum += vsUniforms.nodesMatrices[in.skin[1]] * in.boneWeight[1];
|
|
513
|
+
sum += vsUniforms.nodesMatrices[in.skin[2]] * in.boneWeight[2];
|
|
514
|
+
sum += vsUniforms.nodesMatrices[in.skin[3]] * in.boneWeight[3];
|
|
515
|
+
|
|
516
|
+
let rotation: mat3x3f = mat3x3f(sum[0].xyz, sum[1].xyz, sum[2].xyz);
|
|
517
|
+
|
|
518
|
+
position = sum * position;
|
|
519
|
+
position.w = 1;
|
|
520
|
+
|
|
521
|
+
var out: VSOut;
|
|
522
|
+
out.position = vsUniforms.pMatrix * vsUniforms.mvMatrix * position;
|
|
523
|
+
out.textureCoord = in.textureCoord;
|
|
524
|
+
out.normal = in.normal;
|
|
525
|
+
|
|
526
|
+
var normal: vec3f = in.normal;
|
|
527
|
+
var tangent: vec3f = in.tangent.xyz;
|
|
528
|
+
|
|
529
|
+
// https://learnopengl.com/Advanced-Lighting/Normal-Mapping
|
|
530
|
+
tangent = normalize(tangent - dot(tangent, normal) * normal);
|
|
531
|
+
|
|
532
|
+
var binormal: vec3f = cross(normal, tangent) * in.tangent.w;
|
|
533
|
+
|
|
534
|
+
normal = normalize(rotation * normal);
|
|
535
|
+
tangent = normalize(rotation * tangent);
|
|
536
|
+
binormal = normalize(rotation * binormal);
|
|
537
|
+
|
|
538
|
+
out.normal = normal;
|
|
539
|
+
out.tangent = tangent;
|
|
540
|
+
out.binormal = binormal;
|
|
541
|
+
|
|
542
|
+
out.fragPos = position.xyz;
|
|
543
|
+
|
|
544
|
+
return out;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
fn hypot(z: vec2f) -> f32 {
|
|
548
|
+
var t: f32 = 0;
|
|
549
|
+
var x: f32 = abs(z.x);
|
|
550
|
+
let y: f32 = abs(z.y);
|
|
551
|
+
t = min(x, y);
|
|
552
|
+
x = max(x, y);
|
|
553
|
+
t = t / x;
|
|
554
|
+
if (z.x == 0.0 && z.y == 0.0) {
|
|
555
|
+
return 0.0;
|
|
556
|
+
}
|
|
557
|
+
return x * sqrt(1.0 + t * t);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const PI: f32 = 3.14159265359;
|
|
561
|
+
const gamma: f32 = 2.2;
|
|
562
|
+
const MAX_REFLECTION_LOD: f32 = \${MAX_ENV_MIP_LEVELS};
|
|
563
|
+
|
|
564
|
+
fn distributionGGX(normal: vec3f, halfWay: vec3f, roughness: f32) -> f32 {
|
|
565
|
+
let a: f32 = roughness * roughness;
|
|
566
|
+
let a2: f32 = a * a;
|
|
567
|
+
let nDotH: f32 = max(dot(normal, halfWay), 0.0);
|
|
568
|
+
let nDotH2: f32 = nDotH * nDotH;
|
|
569
|
+
|
|
570
|
+
let num: f32 = a2;
|
|
571
|
+
var denom: f32 = (nDotH2 * (a2 - 1.0) + 1.0);
|
|
572
|
+
denom = PI * denom * denom;
|
|
573
|
+
|
|
574
|
+
return num / denom;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
fn geometrySchlickGGX(nDotV: f32, roughness: f32) -> f32 {
|
|
578
|
+
let r: f32 = roughness + 1.;
|
|
579
|
+
let k: f32 = r * r / 8.;
|
|
580
|
+
// float k = roughness * roughness / 2.;
|
|
581
|
+
|
|
582
|
+
let num: f32 = nDotV;
|
|
583
|
+
let denom: f32 = nDotV * (1. - k) + k;
|
|
584
|
+
|
|
585
|
+
return num / denom;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
fn geometrySmith(normal: vec3f, viewDir: vec3f, lightDir: vec3f, roughness: f32) -> f32 {
|
|
589
|
+
let nDotV: f32 = max(dot(normal, viewDir), .0);
|
|
590
|
+
let nDotL: f32 = max(dot(normal, lightDir), .0);
|
|
591
|
+
let ggx2: f32 = geometrySchlickGGX(nDotV, roughness);
|
|
592
|
+
let ggx1: f32 = geometrySchlickGGX(nDotL, roughness);
|
|
593
|
+
|
|
594
|
+
return ggx1 * ggx2;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
fn fresnelSchlick(lightFactor: f32, f0: vec3f) -> vec3f {
|
|
598
|
+
return f0 + (1. - f0) * pow(clamp(1. - lightFactor, 0., 1.), 5.);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
fn fresnelSchlickRoughness(lightFactor: f32, f0: vec3f, roughness: f32) -> vec3f {
|
|
602
|
+
return f0 + (max(vec3(1.0 - roughness), f0) - f0) * pow(clamp(1.0 - lightFactor, 0.0, 1.0), 5.0);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
@fragment fn fs(
|
|
606
|
+
in: VSOut,
|
|
607
|
+
@builtin(front_facing) isFront: bool
|
|
608
|
+
) -> @location(0) vec4f {
|
|
609
|
+
if (fsUniforms.wireframe > 0) {
|
|
610
|
+
return vec4f(1);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
let texCoord: vec2f = (fsUniforms.tVertexAnim * vec3f(in.textureCoord.x, in.textureCoord.y, 1.)).xy;
|
|
614
|
+
var baseColor: vec4f = textureSample(fsUniformDiffuseTexture, fsUniformDiffuseSampler, texCoord);
|
|
615
|
+
|
|
616
|
+
// hand-made alpha-test
|
|
617
|
+
if (baseColor.a < fsUniforms.discardAlphaLevel) {
|
|
618
|
+
discard;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
let orm: vec4f = textureSample(fsUniformOrmTexture, fsUniformOrmSampler, texCoord);
|
|
622
|
+
|
|
623
|
+
let occlusion: f32 = orm.r;
|
|
624
|
+
let roughness: f32 = orm.g;
|
|
625
|
+
let metallic: f32 = orm.b;
|
|
626
|
+
let teamColorFactor: f32 = orm.a;
|
|
627
|
+
|
|
628
|
+
var teamColor: vec3f = baseColor.rgb * fsUniforms.replaceableColor;
|
|
629
|
+
baseColor = vec4(mix(baseColor.rgb, teamColor, teamColorFactor), baseColor.a);
|
|
630
|
+
baseColor = vec4(pow(baseColor.rgb, vec3f(gamma)), baseColor.a);
|
|
631
|
+
|
|
632
|
+
let TBN: mat3x3f = mat3x3f(in.tangent, in.binormal, in.normal);
|
|
633
|
+
|
|
634
|
+
var normal: vec3f = textureSample(fsUniformNormalTexture, fsUniformNormalSampler, texCoord).xyz;
|
|
635
|
+
normal = normal * 2 - 1;
|
|
636
|
+
normal.x = -normal.x;
|
|
637
|
+
normal.y = -normal.y;
|
|
638
|
+
if (!isFront) {
|
|
639
|
+
normal = -normal;
|
|
640
|
+
}
|
|
641
|
+
normal = normalize(TBN * -normal);
|
|
642
|
+
|
|
643
|
+
let viewDir: vec3f = normalize(fsUniforms.cameraPos - in.fragPos);
|
|
644
|
+
let reflected = reflect(-viewDir, normal);
|
|
645
|
+
|
|
646
|
+
let lightDir: vec3f = normalize(fsUniforms.lightPos - in.fragPos);
|
|
647
|
+
let lightFactor: f32 = max(dot(normal, lightDir), 0);
|
|
648
|
+
let radiance: vec3f = fsUniforms.lightColor;
|
|
649
|
+
|
|
650
|
+
var f0 = vec3f(.04);
|
|
651
|
+
f0 = mix(f0, baseColor.rgb, metallic);
|
|
652
|
+
|
|
653
|
+
var totalLight: vec3f = vec3f(0);
|
|
654
|
+
let halfWay: vec3f = normalize(viewDir + lightDir);
|
|
655
|
+
let ndf: f32 = distributionGGX(normal, halfWay, roughness);
|
|
656
|
+
let g: f32 = geometrySmith(normal, viewDir, lightDir, roughness);
|
|
657
|
+
let f: vec3f = fresnelSchlick(max(dot(halfWay, viewDir), 0), f0);
|
|
658
|
+
|
|
659
|
+
let kS = f;
|
|
660
|
+
var kD = vec3f(1);// - kS;
|
|
661
|
+
if (fsUniforms.hasEnv > 0) {
|
|
662
|
+
kD *= 1 - metallic;
|
|
663
|
+
}
|
|
664
|
+
let num: vec3f = ndf * g * f;
|
|
665
|
+
let denom: f32 = 4. * max(dot(normal, viewDir), 0.) * max(dot(normal, lightDir), 0.) + .0001;
|
|
666
|
+
var specular: vec3f = num / denom;
|
|
667
|
+
|
|
668
|
+
totalLight = (kD * baseColor.rgb / PI + specular) * radiance * lightFactor;
|
|
669
|
+
|
|
670
|
+
if (fsUniforms.shadowParams[0] > .5) {
|
|
671
|
+
let shadowBias: f32 = fsUniforms.shadowParams[1];
|
|
672
|
+
let shadowStep: f32 = fsUniforms.shadowParams[2];
|
|
673
|
+
let fragInLightPos: vec4f = fsUniforms.shadowMapLightMatrix * vec4f(in.fragPos, 1.);
|
|
674
|
+
var shadowMapCoord: vec3f = fragInLightPos.xyz / fragInLightPos.w;
|
|
675
|
+
shadowMapCoord = vec3f((shadowMapCoord.xy + 1) * .5, shadowMapCoord.z);
|
|
676
|
+
shadowMapCoord.y = 1 - shadowMapCoord.y;
|
|
677
|
+
|
|
678
|
+
let passes: u32 = 5;
|
|
679
|
+
let step: f32 = 1. / f32(passes);
|
|
680
|
+
|
|
681
|
+
let currentDepth: f32 = shadowMapCoord.z;
|
|
682
|
+
var lightDepth: f32 = textureSampleCompare(fsUniformShadowTexture, fsUniformShadowSampler, shadowMapCoord.xy, currentDepth - shadowBias);
|
|
683
|
+
let lightDepth0: f32 = textureSampleCompare(fsUniformShadowTexture, fsUniformShadowSampler, vec2f(shadowMapCoord.x + shadowStep, shadowMapCoord.y), currentDepth - shadowBias);
|
|
684
|
+
let lightDepth1: f32 = textureSampleCompare(fsUniformShadowTexture, fsUniformShadowSampler, vec2f(shadowMapCoord.x, shadowMapCoord.y + shadowStep), currentDepth - shadowBias);
|
|
685
|
+
let lightDepth2: f32 = textureSampleCompare(fsUniformShadowTexture, fsUniformShadowSampler, vec2f(shadowMapCoord.x, shadowMapCoord.y - shadowStep), currentDepth - shadowBias);
|
|
686
|
+
let lightDepth3: f32 = textureSampleCompare(fsUniformShadowTexture, fsUniformShadowSampler, vec2f(shadowMapCoord.x - shadowStep, shadowMapCoord.y), currentDepth - shadowBias);
|
|
687
|
+
|
|
688
|
+
var visibility: f32 = 0.;
|
|
689
|
+
if (lightDepth > .5) {
|
|
690
|
+
visibility += step;
|
|
691
|
+
}
|
|
692
|
+
if (lightDepth0 > .5) {
|
|
693
|
+
visibility += step;
|
|
694
|
+
}
|
|
695
|
+
if (lightDepth1 > .5) {
|
|
696
|
+
visibility += step;
|
|
697
|
+
}
|
|
698
|
+
if (lightDepth2 > .5) {
|
|
699
|
+
visibility += step;
|
|
700
|
+
}
|
|
701
|
+
if (lightDepth3 > .5) {
|
|
702
|
+
visibility += step;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
totalLight *= visibility;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
var color: vec3f = vec3f(0.0);
|
|
709
|
+
|
|
710
|
+
if (fsUniforms.hasEnv > 0) {
|
|
711
|
+
let f: vec3f = fresnelSchlickRoughness(max(dot(normal, viewDir), 0.0), f0, roughness);
|
|
712
|
+
let kS: vec3f = f;
|
|
713
|
+
var kD: vec3f = vec3f(1.0) - kS;
|
|
714
|
+
kD *= 1.0 - metallic;
|
|
715
|
+
|
|
716
|
+
let diffuse: vec3f = textureSample(irradienceMapTexture, irradienceMapSampler, normal).rgb * baseColor.rgb;
|
|
717
|
+
let prefilteredColor: vec3f = textureSampleLevel(prefilteredEnvTexture, prefilteredEnvSampler, reflected, roughness * MAX_REFLECTION_LOD).rgb;
|
|
718
|
+
let envBRDF: vec2f = textureSample(brdfLutTexture, brdfLutSampler, vec2f(max(dot(normal, viewDir), 0.0), roughness)).rg;
|
|
719
|
+
specular = prefilteredColor * (f * envBRDF.x + envBRDF.y);
|
|
720
|
+
|
|
721
|
+
let ambient: vec3f = (kD * diffuse + specular) * occlusion;
|
|
722
|
+
color = ambient + totalLight;
|
|
723
|
+
} else {
|
|
724
|
+
var ambient: vec3f = vec3(.03);
|
|
725
|
+
ambient *= baseColor.rgb * occlusion;
|
|
726
|
+
color = ambient + totalLight;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
color = color / (vec3f(1) + color);
|
|
730
|
+
color = pow(color, vec3f(1 / gamma));
|
|
731
|
+
|
|
732
|
+
return vec4f(color, baseColor.a);
|
|
733
|
+
}
|
|
734
|
+
`,er=`struct VSUniforms {
|
|
735
|
+
mvMatrix: mat4x4f,
|
|
736
|
+
pMatrix: mat4x4f,
|
|
737
|
+
nodesMatrices: array<mat4x4f, \${MAX_NODES}>,
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
struct FSUniforms {
|
|
741
|
+
replaceableColor: vec3f,
|
|
742
|
+
// replaceableType: u32,
|
|
743
|
+
discardAlphaLevel: f32,
|
|
744
|
+
tVertexAnim: mat3x3f,
|
|
745
|
+
lightPos: vec3f,
|
|
746
|
+
lightColor: vec3f,
|
|
747
|
+
cameraPos: vec3f,
|
|
748
|
+
shadowParams: vec3f,
|
|
749
|
+
shadowMapLightMatrix: mat4x4f,
|
|
750
|
+
// env
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
@group(0) @binding(0) var<uniform> vsUniforms: VSUniforms;
|
|
754
|
+
@group(1) @binding(0) var<uniform> fsUniforms: FSUniforms;
|
|
755
|
+
@group(1) @binding(1) var fsUniformDiffuseSampler: sampler;
|
|
756
|
+
@group(1) @binding(2) var fsUniformDiffuseTexture: texture_2d<f32>;
|
|
757
|
+
@group(1) @binding(3) var fsUniformNormalSampler: sampler;
|
|
758
|
+
@group(1) @binding(4) var fsUniformNormalTexture: texture_2d<f32>;
|
|
759
|
+
@group(1) @binding(5) var fsUniformOrmSampler: sampler;
|
|
760
|
+
@group(1) @binding(6) var fsUniformOrmTexture: texture_2d<f32>;
|
|
761
|
+
@group(1) @binding(7) var fsUniformShadowSampler: sampler_comparison;
|
|
762
|
+
// @group(1) @binding(7) var fsUniformShadowSampler: sampler;
|
|
763
|
+
@group(1) @binding(8) var fsUniformShadowTexture: texture_depth_2d;
|
|
764
|
+
|
|
765
|
+
struct VSIn {
|
|
766
|
+
@location(0) vertexPosition: vec3f,
|
|
767
|
+
@location(1) normal: vec3f,
|
|
768
|
+
@location(2) textureCoord: vec2f,
|
|
769
|
+
@location(3) tangent: vec4f,
|
|
770
|
+
@location(4) skin: vec4<u32>,
|
|
771
|
+
@location(5) boneWeight: vec4f,
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
struct VSOut {
|
|
775
|
+
@builtin(position) position: vec4f,
|
|
776
|
+
@location(0) textureCoord: vec2f,
|
|
777
|
+
@location(1) depth: f32,
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
@vertex fn vs(
|
|
781
|
+
in: VSIn
|
|
782
|
+
) -> VSOut {
|
|
783
|
+
var position: vec4f = vec4f(in.vertexPosition, 1.0);
|
|
784
|
+
var sum: mat4x4f;
|
|
785
|
+
|
|
786
|
+
sum += vsUniforms.nodesMatrices[in.skin[0]] * in.boneWeight[0];
|
|
787
|
+
sum += vsUniforms.nodesMatrices[in.skin[1]] * in.boneWeight[1];
|
|
788
|
+
sum += vsUniforms.nodesMatrices[in.skin[2]] * in.boneWeight[2];
|
|
789
|
+
sum += vsUniforms.nodesMatrices[in.skin[3]] * in.boneWeight[3];
|
|
790
|
+
|
|
791
|
+
position = sum * position;
|
|
792
|
+
position.w = 1;
|
|
793
|
+
|
|
794
|
+
var out: VSOut;
|
|
795
|
+
out.position = vsUniforms.pMatrix * vsUniforms.mvMatrix * position;
|
|
796
|
+
out.textureCoord = in.textureCoord;
|
|
797
|
+
|
|
798
|
+
out.depth = out.position.z / out.position.w;
|
|
799
|
+
|
|
800
|
+
return out;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
struct FSOut {
|
|
804
|
+
@builtin(frag_depth) depth: f32,
|
|
805
|
+
@location(0) color: vec4f
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
@fragment fn fs(
|
|
809
|
+
in: VSOut,
|
|
810
|
+
@builtin(front_facing) isFront: bool
|
|
811
|
+
) -> FSOut {
|
|
812
|
+
let texCoord: vec2f = (fsUniforms.tVertexAnim * vec3f(in.textureCoord.x, in.textureCoord.y, 1.)).xy;
|
|
813
|
+
var baseColor: vec4f = textureSample(fsUniformDiffuseTexture, fsUniformDiffuseSampler, texCoord);
|
|
814
|
+
|
|
815
|
+
// hand-made alpha-test
|
|
816
|
+
if (baseColor.a < fsUniforms.discardAlphaLevel) {
|
|
817
|
+
discard;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
var out: FSOut;
|
|
821
|
+
out.color = vec4f(1, 1, 1, 1);
|
|
822
|
+
out.depth = in.depth;
|
|
823
|
+
return out;
|
|
824
|
+
}
|
|
825
|
+
`;var he=254;var Be=8;var cr=$e.replace(/\$\{MAX_NODES}/g,String(he)),dr=Ye.replace(/\$\{MAX_NODES}/g,String(he)),gr=je.replace(/\$\{MAX_NODES}/g,String(he)),pr=Ze.replace(/\$\{MAX_ENV_MIP_LEVELS}/g,String(Be.toFixed(1))),mr=Qe.replace(/\$\{MAX_NODES}/g,String(he)),vr=Je.replace(/\$\{MAX_NODES}/g,String(he)).replace(/\$\{MAX_ENV_MIP_LEVELS}/g,String(Be.toFixed(1))),Tr=er.replace(/\$\{MAX_NODES}/g,String(he)),br=G(),xr=Te(),Ar=G(),Pr=ve(0,0,0),Sr=Ke(0,0,0,1),yr=ve(1,1,1),Er=Te(),Lr=Ae(),Fr=Ae(),Ur=G(),Cr=G(),Br=Te(),Vr=Ae(),Mr=G(),Dr=G(),Rr=G(),wr=G(),Gr=G(),Ir=G(),_r=G(),Or=Le(),Nr=Ae(),kr=Le();var exports={decodeBLP:Fe,getBLPImageData:Ue};if(typeof module!=="undefined"&&module.exports){module.exports=exports}else{globalThis.war3modelBlp=exports}})();
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>war3-model-blp browser demo</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
color-scheme: light dark;
|
|
10
|
+
font-family: Arial, sans-serif;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
body {
|
|
14
|
+
margin: 24px;
|
|
15
|
+
max-width: 860px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
main {
|
|
19
|
+
display: grid;
|
|
20
|
+
gap: 16px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.toolbar {
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: center;
|
|
26
|
+
gap: 12px;
|
|
27
|
+
flex-wrap: wrap;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
canvas {
|
|
31
|
+
image-rendering: pixelated;
|
|
32
|
+
max-width: 100%;
|
|
33
|
+
border: 1px solid #888;
|
|
34
|
+
background:
|
|
35
|
+
linear-gradient(45deg, #ccc 25%, transparent 25%),
|
|
36
|
+
linear-gradient(-45deg, #ccc 25%, transparent 25%),
|
|
37
|
+
linear-gradient(45deg, transparent 75%, #ccc 75%),
|
|
38
|
+
linear-gradient(-45deg, transparent 75%, #ccc 75%);
|
|
39
|
+
background-position: 0 0, 0 8px, 8px -8px, -8px 0;
|
|
40
|
+
background-size: 16px 16px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#status {
|
|
44
|
+
min-height: 1.4em;
|
|
45
|
+
}
|
|
46
|
+
</style>
|
|
47
|
+
</head>
|
|
48
|
+
<body>
|
|
49
|
+
<main>
|
|
50
|
+
<h1>war3-model-blp browser demo</h1>
|
|
51
|
+
|
|
52
|
+
<div class="toolbar">
|
|
53
|
+
<input id="file" type="file" accept=".blp">
|
|
54
|
+
<span id="status">Choose a BLP1 file.</span>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<canvas id="preview" width="1" height="1"></canvas>
|
|
58
|
+
</main>
|
|
59
|
+
|
|
60
|
+
<script src="../dist/war3-model-blp.js"></script>
|
|
61
|
+
<script>
|
|
62
|
+
const fileInput = document.getElementById("file");
|
|
63
|
+
const status = document.getElementById("status");
|
|
64
|
+
const canvas = document.getElementById("preview");
|
|
65
|
+
const context = canvas.getContext("2d");
|
|
66
|
+
const { decodeBLP, getBLPImageData } = globalThis.war3modelBlp;
|
|
67
|
+
|
|
68
|
+
fileInput.addEventListener("change", async () => {
|
|
69
|
+
const file = fileInput.files[0];
|
|
70
|
+
|
|
71
|
+
if (!file) {
|
|
72
|
+
status.textContent = "Choose a BLP1 file.";
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const buffer = await file.arrayBuffer();
|
|
78
|
+
const blp = decodeBLP(buffer);
|
|
79
|
+
const imageData = getBLPImageData(blp, 0);
|
|
80
|
+
|
|
81
|
+
canvas.width = imageData.width;
|
|
82
|
+
canvas.height = imageData.height;
|
|
83
|
+
context.putImageData(imageData, 0, 0);
|
|
84
|
+
|
|
85
|
+
status.textContent = `${file.name}: ${imageData.width} x ${imageData.height}`;
|
|
86
|
+
} catch (error) {
|
|
87
|
+
status.textContent = error instanceof Error ? error.message : String(error);
|
|
88
|
+
canvas.width = 1;
|
|
89
|
+
canvas.height = 1;
|
|
90
|
+
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
</script>
|
|
94
|
+
</body>
|
|
95
|
+
</html>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const fs = require("node:fs");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
const { decodeBLP, getBLPImageData } = require("../dist/war3-model-blp.js");
|
|
4
|
+
|
|
5
|
+
const filePath = process.argv[2];
|
|
6
|
+
|
|
7
|
+
if (!filePath) {
|
|
8
|
+
console.error("Usage: node examples/node.cjs <file.blp>");
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const buffer = fs.readFileSync(filePath);
|
|
13
|
+
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
14
|
+
const blp = decodeBLP(arrayBuffer);
|
|
15
|
+
const imageData = getBLPImageData(blp, 0);
|
|
16
|
+
|
|
17
|
+
console.log(`${path.basename(filePath)}: ${imageData.width} x ${imageData.height}`);
|
|
18
|
+
console.log(`pixels: ${imageData.data.length} bytes`);
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "war3-model-blp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lightweight BLP1 decoder extracted from war3-model.",
|
|
5
|
+
"main": "dist/war3-model-blp.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"examples",
|
|
9
|
+
"LICENSE",
|
|
10
|
+
"NOTICE",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "npm run test:node",
|
|
15
|
+
"test:node": "node test/node.cjs"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"warcraft",
|
|
19
|
+
"warcraft3",
|
|
20
|
+
"war3",
|
|
21
|
+
"blp",
|
|
22
|
+
"blp1",
|
|
23
|
+
"decoder"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"homepage": "https://github.com/giveyousomecolorlooklook/war3-model-blp#readme",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/giveyousomecolorlooklook/war3-model-blp.git"
|
|
30
|
+
},
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/giveyousomecolorlooklook/war3-model-blp/issues"
|
|
33
|
+
}
|
|
34
|
+
}
|