|
@@ -15,7 +15,9 @@ class PbrShader extends hxsl.Shader {
|
|
|
|
|
|
@param var roughness : Float;
|
|
@param var roughness : Float;
|
|
|
|
|
|
- @param var cubeMap : SamplerCube;
|
|
|
|
|
|
+ @param var envMap : SamplerCube;
|
|
|
|
+ @param var irrDiffuse : SamplerCube;
|
|
|
|
+ @param var irrSpecular : SamplerCube;
|
|
|
|
|
|
var l : Vec3;
|
|
var l : Vec3;
|
|
var n : Vec3;
|
|
var n : Vec3;
|
|
@@ -105,13 +107,12 @@ class PbrShader extends hxsl.Shader {
|
|
|
|
|
|
// ------------- INDIRECT LIGHT -------------------------
|
|
// ------------- INDIRECT LIGHT -------------------------
|
|
|
|
|
|
- var diffuse = vec3(0, 0, 0); // TODO - generate Irradiance EnvMap from CubeMap
|
|
|
|
|
|
+ var diffuse = irrDiffuse.get(normal).rgb.pow(vec3(2.2)) * diffuseColor;
|
|
var specular = vec3(0, 0, 0); // TODO - generate PMREM from CubeMap
|
|
var specular = vec3(0, 0, 0); // TODO - generate PMREM from CubeMap
|
|
var indirect = diffuse + specular;
|
|
var indirect = diffuse + specular;
|
|
|
|
|
|
color = direct + indirect;
|
|
color = direct + indirect;
|
|
|
|
|
|
-
|
|
|
|
// reinhard tonemapping
|
|
// reinhard tonemapping
|
|
color *= exp(exposure);
|
|
color *= exp(exposure);
|
|
color = color / (color + vec3(1.));
|
|
color = color / (color + vec3(1.));
|
|
@@ -148,6 +149,14 @@ class Irradiance extends h3d.shader.ScreenShader {
|
|
@const var samplesBits : Int;
|
|
@const var samplesBits : Int;
|
|
@param var envMap : SamplerCube;
|
|
@param var envMap : SamplerCube;
|
|
|
|
|
|
|
|
+ @const var isSpecular : Bool;
|
|
|
|
+ @param var roughness : Float;
|
|
|
|
+
|
|
|
|
+ #if (js || flash)
|
|
|
|
+ @const var SamplesCount : Int;
|
|
|
|
+ @param var hammerTbl : Array<Vec4,SamplesCount>;
|
|
|
|
+ #end
|
|
|
|
+
|
|
function _reversebits( i : Int ) : Int {
|
|
function _reversebits( i : Int ) : Int {
|
|
var r = (i << 16) | (i >>> 16);
|
|
var r = (i << 16) | (i >>> 16);
|
|
r = ((r & 0x00ff00ff) << 8) | ((r & 0xff00ff00) >>> 8);
|
|
r = ((r & 0x00ff00ff) << 8) | ((r & 0xff00ff00) >>> 8);
|
|
@@ -158,8 +167,12 @@ class Irradiance extends h3d.shader.ScreenShader {
|
|
}
|
|
}
|
|
|
|
|
|
function hammersley( i : Int, max : Int ) : Vec2 {
|
|
function hammersley( i : Int, max : Int ) : Vec2 {
|
|
|
|
+ #if (js || flash)
|
|
|
|
+ return hammerTbl[i].xy;
|
|
|
|
+ #else
|
|
var ri = _reversebits(i) * 2.3283064365386963e-10;
|
|
var ri = _reversebits(i) * 2.3283064365386963e-10;
|
|
return vec2(float(i) / float(max), ri);
|
|
return vec2(float(i) / float(max), ri);
|
|
|
|
+ #end
|
|
}
|
|
}
|
|
|
|
|
|
function cosineWeightedSampling( p : Vec2 ) : Vec3 {
|
|
function cosineWeightedSampling( p : Vec2 ) : Vec3 {
|
|
@@ -172,8 +185,12 @@ class Irradiance extends h3d.shader.ScreenShader {
|
|
var d = input.uv * 2. - 1.;
|
|
var d = input.uv * 2. - 1.;
|
|
var n : Vec3;
|
|
var n : Vec3;
|
|
switch( face ) {
|
|
switch( face ) {
|
|
- case 0: n = vec3(1, -d.y, -d.x);
|
|
|
|
- default: n = vec3( -d.x, -d.y, -1);
|
|
|
|
|
|
+ case 0: n = vec3(1, d.y, -d.x);
|
|
|
|
+ case 1: n = vec3(-1, d.y, d.x);
|
|
|
|
+ case 2: n = vec3(d.x, 1, -d.y);
|
|
|
|
+ case 3: n = vec3(d.x, -1, d.y);
|
|
|
|
+ case 4: n = vec3(d.x, d.y, 1);
|
|
|
|
+ default: n = vec3(-d.x, d.y,-1);
|
|
}
|
|
}
|
|
return n.normalize();
|
|
return n.normalize();
|
|
}
|
|
}
|
|
@@ -183,18 +200,33 @@ class Irradiance extends h3d.shader.ScreenShader {
|
|
var n = getNormal();
|
|
var n = getNormal();
|
|
var up = abs(n.z) < 0.999 ? vec3(0, 0, 1) : vec3(1, 0, 0);
|
|
var up = abs(n.z) < 0.999 ? vec3(0, 0, 1) : vec3(1, 0, 0);
|
|
var tanX = normalize(cross(up, n));
|
|
var tanX = normalize(cross(up, n));
|
|
- var tanY = cross(n, tanX);
|
|
|
|
|
|
+ var tanY = normalize(cross(n, tanX));
|
|
var totalWeight = 1e-10;
|
|
var totalWeight = 1e-10;
|
|
var numSamples = 1 << samplesBits;
|
|
var numSamples = 1 << samplesBits;
|
|
- for( i in 0...numSamples ) {
|
|
|
|
|
|
+ for( i in 0...1 << samplesBits ) {
|
|
var p = hammersley(i, numSamples);
|
|
var p = hammersley(i, numSamples);
|
|
- var ltan = cosineWeightedSampling(p);
|
|
|
|
- var l = normalize(tanX * ltan.x + tanY * ltan.y + n * ltan.z);
|
|
|
|
|
|
+ var l : Vec3;
|
|
|
|
+ if( isSpecular ) {
|
|
|
|
+ // importance sampling GGX BRDF
|
|
|
|
+ var a = roughness * roughness;
|
|
|
|
+ var phi = 2 * PI * p.x;
|
|
|
|
+ var cosT = sqrt((1 - p.y) / (1 + (a * a - 1) * p.y));
|
|
|
|
+ var sinT = sqrt(1 - cosT * cosT);
|
|
|
|
+ var ltan = vec3(sinT * cos(phi), sinT * sin(phi), cosT);
|
|
|
|
+ var h = tanX * ltan.x + tanY * ltan.y + n * ltan.z;
|
|
|
|
+ var v = n; // approx
|
|
|
|
+ l = -reflect(v, h);
|
|
|
|
+ } else {
|
|
|
|
+ var ltan = cosineWeightedSampling(p);
|
|
|
|
+ l = tanX * ltan.x + tanY * ltan.y + n * ltan.z;
|
|
|
|
+ }
|
|
var amount = n.dot(l);
|
|
var amount = n.dot(l);
|
|
- color += envMap.get(l).rgb * amount;
|
|
|
|
- totalWeight += amount;
|
|
|
|
|
|
+ if( amount > 0 ) {
|
|
|
|
+ color += envMap.get(l).rgb.pow(vec3(2.2)) * amount;
|
|
|
|
+ totalWeight += amount;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- output.color = vec4(color / totalWeight, 1.);
|
|
|
|
|
|
+ output.color = vec4((color / totalWeight).pow(vec3(1/2.2)), 1.);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
@@ -214,8 +246,20 @@ class Pbr extends hxd.App {
|
|
var color : h2d.Bitmap;
|
|
var color : h2d.Bitmap;
|
|
var sphere : h3d.scene.Mesh;
|
|
var sphere : h3d.scene.Mesh;
|
|
|
|
|
|
|
|
+ var sampleBits : Int;
|
|
|
|
+
|
|
|
|
+ var envMap : h3d.mat.Texture;
|
|
|
|
+ var irradDiffuse : h3d.mat.Texture;
|
|
|
|
+ var irradSpecular : h3d.mat.Texture;
|
|
|
|
+
|
|
override function init() {
|
|
override function init() {
|
|
|
|
|
|
|
|
+ #if flash
|
|
|
|
+ engine.debug = true;
|
|
|
|
+ #end
|
|
|
|
+
|
|
|
|
+ //displaySampling(256, new h3d.Vector(1, 0., 0.), 0.5);
|
|
|
|
+
|
|
font = hxd.res.DefaultFont.get();
|
|
font = hxd.res.DefaultFont.get();
|
|
|
|
|
|
var sp = new h3d.prim.Sphere(1, 128, 128);
|
|
var sp = new h3d.prim.Sphere(1, 128, 128);
|
|
@@ -245,17 +289,14 @@ class Pbr extends hxd.App {
|
|
g.lineTo(0, 0, 2);
|
|
g.lineTo(0, 0, 2);
|
|
g.lineStyle();
|
|
g.lineStyle();
|
|
|
|
|
|
- engine.debug = true;
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- var cube = new h3d.mat.Texture(512, 512, [Cubic]);
|
|
|
|
|
|
+ envMap = new h3d.mat.Texture(512, 512, [Cubic]);
|
|
inline function set(face:Int, res:hxd.res.Image) {
|
|
inline function set(face:Int, res:hxd.res.Image) {
|
|
#if flash
|
|
#if flash
|
|
// all mipmap levels required
|
|
// all mipmap levels required
|
|
var bmp = res.toBitmap();
|
|
var bmp = res.toBitmap();
|
|
var level = 0;
|
|
var level = 0;
|
|
while( true ) {
|
|
while( true ) {
|
|
- cube.uploadBitmap(bmp, level++, face);
|
|
|
|
|
|
+ envMap.uploadBitmap(bmp, level++, face);
|
|
if( bmp.width == 1 ) break;
|
|
if( bmp.width == 1 ) break;
|
|
var sub = new hxd.BitmapData(bmp.width >> 1, bmp.height >> 1);
|
|
var sub = new hxd.BitmapData(bmp.width >> 1, bmp.height >> 1);
|
|
sub.drawScaled(0, 0, sub.width, sub.height, bmp, 0, 0, bmp.width, bmp.height);
|
|
sub.drawScaled(0, 0, sub.width, sub.height, bmp, 0, 0, bmp.width, bmp.height);
|
|
@@ -265,7 +306,7 @@ class Pbr extends hxd.App {
|
|
bmp.dispose();
|
|
bmp.dispose();
|
|
#else
|
|
#else
|
|
var pix = res.getPixels();
|
|
var pix = res.getPixels();
|
|
- cube.uploadPixels(pix, 0, face);
|
|
|
|
|
|
+ envMap.uploadPixels(pix, 0, face);
|
|
#end
|
|
#end
|
|
}
|
|
}
|
|
set(0, hxd.Res.front);
|
|
set(0, hxd.Res.front);
|
|
@@ -275,21 +316,24 @@ class Pbr extends hxd.App {
|
|
set(4, hxd.Res.top);
|
|
set(4, hxd.Res.top);
|
|
set(5, hxd.Res.bottom);
|
|
set(5, hxd.Res.bottom);
|
|
|
|
|
|
- shader.cubeMap = cube;
|
|
|
|
|
|
|
|
|
|
+ var size = 64;
|
|
|
|
+ #if (js || flash)
|
|
|
|
+ sampleBits = 5;
|
|
|
|
+ size = 16;
|
|
|
|
+ #else
|
|
|
|
+ sampleBits = 10;
|
|
|
|
+ #end
|
|
|
|
|
|
- var irradDiffuse = new h3d.mat.Texture(128, 128, [Cubic]);
|
|
|
|
- var screen = new h3d.pass.ScreenFx(new Irradiance());
|
|
|
|
- screen.shader.samplesBits = 8;
|
|
|
|
- screen.shader.envMap = cube;
|
|
|
|
- for( i in 0...6 ) {
|
|
|
|
- screen.shader.face = i;
|
|
|
|
- engine.driver.setRenderTarget(irradDiffuse, i);
|
|
|
|
- screen.render();
|
|
|
|
- }
|
|
|
|
- engine.driver.setRenderTarget(null);
|
|
|
|
|
|
+ irradDiffuse = new h3d.mat.Texture(size, size, [Cubic]);
|
|
|
|
+ irradSpecular = new h3d.mat.Texture(size, size, [Cubic, MipMapped]);
|
|
|
|
+ computeIrradiance();
|
|
|
|
|
|
- bg.material.mainPass.addShader(new h3d.shader.CubeMap(cube));
|
|
|
|
|
|
+ shader.envMap = envMap;
|
|
|
|
+ shader.irrDiffuse = irradDiffuse;
|
|
|
|
+ shader.irrSpecular = irradSpecular;
|
|
|
|
+
|
|
|
|
+ var cubeShader = bg.material.mainPass.addShader(new h3d.shader.CubeMap(envMap));
|
|
|
|
|
|
shader.metalness = 0.2;
|
|
shader.metalness = 0.2;
|
|
shader.roughness = 0.3;
|
|
shader.roughness = 0.3;
|
|
@@ -308,11 +352,45 @@ class Pbr extends hxd.App {
|
|
addSlider("Hue", 0, Math.PI, function() return hue, function(v) hue = v);
|
|
addSlider("Hue", 0, Math.PI, function() return hue, function(v) hue = v);
|
|
addSlider("Saturation", 0, 1, function() return saturation, function(v) saturation = v);
|
|
addSlider("Saturation", 0, 1, function() return saturation, function(v) saturation = v);
|
|
addSlider("Brightness", 0, 1, function() return brightness, function(v) brightness = v);
|
|
addSlider("Brightness", 0, 1, function() return brightness, function(v) brightness = v);
|
|
-
|
|
|
|
|
|
+ addChoice("Env", ["Default", "IDiff", "ISpec"], function(i) cubeShader.texture = [envMap, irradDiffuse, irradSpecular][i]);
|
|
|
|
|
|
s2d.addEventListener(onEvent);
|
|
s2d.addEventListener(onEvent);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ function computeIrradiance() {
|
|
|
|
+ var screen = new h3d.pass.ScreenFx(new Irradiance());
|
|
|
|
+ screen.shader.samplesBits = sampleBits;
|
|
|
|
+ screen.shader.envMap = envMap;
|
|
|
|
+
|
|
|
|
+ #if (js || flash)
|
|
|
|
+ var nsamples = 1 << sampleBits;
|
|
|
|
+ screen.shader.SamplesCount = nsamples;
|
|
|
|
+ screen.shader.hammerTbl = [for( i in 0...nsamples ) hammersley(i, nsamples)];
|
|
|
|
+ #end
|
|
|
|
+
|
|
|
|
+ for( i in 0...6 ) {
|
|
|
|
+ screen.shader.face = i;
|
|
|
|
+ engine.driver.setRenderTarget(irradDiffuse, i);
|
|
|
|
+ screen.render();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ screen.shader.isSpecular = true;
|
|
|
|
+
|
|
|
|
+ var mipLevels = 1;
|
|
|
|
+ while( irradSpecular.width > 1 << (mipLevels - 1) )
|
|
|
|
+ mipLevels++;
|
|
|
|
+
|
|
|
|
+ for( i in 0...6 ) {
|
|
|
|
+ screen.shader.face = i;
|
|
|
|
+ for( j in 0...mipLevels ) {
|
|
|
|
+ screen.shader.roughness = j / mipLevels;
|
|
|
|
+ engine.driver.setRenderTarget(irradSpecular, i, j);
|
|
|
|
+ screen.render();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ engine.driver.setRenderTarget(null);
|
|
|
|
+ }
|
|
|
|
+
|
|
function onEvent(e:hxd.Event) {
|
|
function onEvent(e:hxd.Event) {
|
|
switch( e.kind ) {
|
|
switch( e.kind ) {
|
|
case EPush:
|
|
case EPush:
|
|
@@ -361,6 +439,31 @@ class Pbr extends hxd.App {
|
|
i.onOut(null);
|
|
i.onOut(null);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ function addChoice( text, choices, callb, value = 0 ) {
|
|
|
|
+ var i = new h2d.Interactive(80, font.lineHeight, fui);
|
|
|
|
+ i.backgroundColor = 0xFF808080;
|
|
|
|
+ fui.getProperties(i).paddingLeft = 20;
|
|
|
|
+
|
|
|
|
+ var t = new h2d.Text(font, i);
|
|
|
|
+ t.maxWidth = i.width;
|
|
|
|
+ t.text = text+":"+choices[value];
|
|
|
|
+ t.textAlign = Center;
|
|
|
|
+
|
|
|
|
+ i.onClick = function(_) {
|
|
|
|
+ value++;
|
|
|
|
+ value %= choices.length;
|
|
|
|
+ callb(value);
|
|
|
|
+ t.text = text + ":" + choices[value];
|
|
|
|
+ };
|
|
|
|
+ i.onOver = function(_) {
|
|
|
|
+ t.textColor = 0xFFFFFF;
|
|
|
|
+ };
|
|
|
|
+ i.onOut = function(_) {
|
|
|
|
+ t.textColor = 0xEEEEEE;
|
|
|
|
+ };
|
|
|
|
+ i.onOut(null);
|
|
|
|
+ }
|
|
|
|
+
|
|
function addSlider( text, min : Float, max : Float, get : Void -> Float, set : Float -> Void ) {
|
|
function addSlider( text, min : Float, max : Float, get : Void -> Float, set : Float -> Void ) {
|
|
var f = new h2d.Flow(fui);
|
|
var f = new h2d.Flow(fui);
|
|
|
|
|
|
@@ -407,6 +510,84 @@ class Pbr extends hxd.App {
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+
|
|
|
|
+ // ----- DEBUG
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ function _reversebits( i : Int ) : Int {
|
|
|
|
+ var r = (i << 16) | (i >>> 16);
|
|
|
|
+ r = ((r & 0x00ff00ff) << 8) | ((r & 0xff00ff00) >>> 8);
|
|
|
|
+ r = ((r & 0x0f0f0f0f) << 4) | ((r & 0xf0f0f0f0) >>> 4);
|
|
|
|
+ r = ((r & 0x33333333) << 2) | ((r & 0xcccccccc) >>> 2);
|
|
|
|
+ r = ((r & 0x55555555) << 1) | ((r & 0xaaaaaaaa) >>> 1);
|
|
|
|
+ return r;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function hammersley( i : Int, max : Int ) : h3d.Vector {
|
|
|
|
+ var ri = _reversebits(i) * 2.3283064365386963e-10;
|
|
|
|
+ return new h3d.Vector(i / max, ri);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function cosineWeightedSampling( p : h3d.Vector ) : h3d.Vector {
|
|
|
|
+ var sq = Math.sqrt(1 - p.x);
|
|
|
|
+ var alpha = 2 * Math.PI * p.y;
|
|
|
|
+ return new h3d.Vector( sq * Math.cos(alpha), sq * Math.sin(alpha), Math.sqrt(p.x));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function displaySampling( max : Int, n : h3d.Vector, ?roughness : Float ) {
|
|
|
|
+ n.normalize();
|
|
|
|
+ var up = n.z < 0.999 ? new h3d.Vector(0, 0, 1) : new h3d.Vector(1, 0, 0);
|
|
|
|
+ var tanX = up.cross(n);
|
|
|
|
+ tanX.normalize();
|
|
|
|
+ var tanY = n.cross(tanX);
|
|
|
|
+ tanY.normalize();
|
|
|
|
+
|
|
|
|
+ var sp = new h3d.prim.Sphere(0.01, 4, 4);
|
|
|
|
+ for( i in 0...max ) {
|
|
|
|
+ var h = hammersley(i, max);
|
|
|
|
+
|
|
|
|
+ var l = new h3d.Vector();
|
|
|
|
+ if( roughness != null ) {
|
|
|
|
+
|
|
|
|
+ var a = roughness * roughness;
|
|
|
|
+ var phi = 2 * Math.PI * h.x;
|
|
|
|
+ var cosT = Math.sqrt((1 - h.y) / (1 + (a * a - 1) * h.y));
|
|
|
|
+ var sinT = Math.sqrt(1 - cosT * cosT);
|
|
|
|
+ var ltan = new h3d.Vector(sinT * Math.cos(phi), sinT * Math.sin(phi), cosT);
|
|
|
|
+
|
|
|
|
+ var hx = tanX.x * ltan.x + tanY.x * ltan.y + n.x * ltan.z;
|
|
|
|
+ var hy = tanX.y * ltan.x + tanY.y * ltan.y + n.y * ltan.z;
|
|
|
|
+ var hz = tanX.z * ltan.x + tanY.z * ltan.y + n.z * ltan.z;
|
|
|
|
+
|
|
|
|
+ var v = n; // approx
|
|
|
|
+
|
|
|
|
+ var hDotV = v.dot3(new h3d.Vector(hx, hy, hz));
|
|
|
|
+ // -reflect(v,h)
|
|
|
|
+ l.x = v.x - 2 * hDotV * h.x;
|
|
|
|
+ l.y = v.y - 2 * hDotV * h.y;
|
|
|
|
+ l.z = v.z - 2 * hDotV * h.z;
|
|
|
|
+
|
|
|
|
+ } else {
|
|
|
|
+
|
|
|
|
+ var c = cosineWeightedSampling(h);
|
|
|
|
+ l.x = tanX.x * c.x + tanY.x * c.y + n.x * c.z;
|
|
|
|
+ l.y = tanX.y * c.x + tanY.y * c.y + n.y * c.z;
|
|
|
|
+ l.z = tanX.z * c.x + tanY.z * c.y + n.z * c.z;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ l.normalize();
|
|
|
|
+
|
|
|
|
+ var s = new h3d.scene.Mesh(sp, s3d);
|
|
|
|
+ s.x = l.x;
|
|
|
|
+ s.y = l.y;
|
|
|
|
+ s.z = l.z;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // ---------------------
|
|
|
|
+
|
|
|
|
+
|
|
static function main() {
|
|
static function main() {
|
|
hxd.Res.initEmbed();
|
|
hxd.Res.initEmbed();
|
|
#if hl
|
|
#if hl
|