|
@@ -4,34 +4,34 @@ class LocalVolumetricShader extends hxsl.Shader {
|
|
|
|
|
|
static var SRC = {
|
|
|
|
|
|
- final EPSILON = 1e-4;
|
|
|
- final FLT_MAX = 3.402823466e+38;
|
|
|
+ final EPSILON = 1e-4;
|
|
|
+ final FLT_MAX = 3.402823466e+38;
|
|
|
|
|
|
- @global var depthMap : Channel;
|
|
|
+ @global var depthMap : Channel;
|
|
|
|
|
|
@global var camera : {
|
|
|
var position : Vec3;
|
|
|
- var inverseViewProj : Mat4;
|
|
|
+ var inverseViewProj : Mat4;
|
|
|
};
|
|
|
|
|
|
- @global var global : {
|
|
|
- @perObject var modelView : Mat4;
|
|
|
+ @global var global : {
|
|
|
+ @perObject var modelView : Mat4;
|
|
|
@perObject var modelViewInverse : Mat4;
|
|
|
};
|
|
|
|
|
|
- @param var obH : Vec3;
|
|
|
+ @param var obH : Vec3;
|
|
|
|
|
|
- @param var fogColor : Vec4;
|
|
|
- @param var fogDensity : Float;
|
|
|
- @param var fogFade : Float;
|
|
|
+ @param var fogColor : Vec4;
|
|
|
+ @param var fogDensity : Float;
|
|
|
+ @param var fogFade : Float;
|
|
|
|
|
|
- var screenUV : Vec2;
|
|
|
- var transformedPosition : Vec3;
|
|
|
- var transformedNormal : Vec3;
|
|
|
- var pixelColor : Vec4;
|
|
|
+ var screenUV : Vec2;
|
|
|
+ var transformedPosition : Vec3;
|
|
|
+ var transformedNormal : Vec3;
|
|
|
+ var pixelColor : Vec4;
|
|
|
|
|
|
- function maxComp(a : Vec3) : Float { return max(a.x, max(a.y, a.z)); }
|
|
|
- function minComp(a : Vec3) : Float { return min(a.x, min(a.y, a.z)); }
|
|
|
+ function maxComp(a : Vec3) : Float { return max(a.x, max(a.y, a.z)); }
|
|
|
+ function minComp(a : Vec3) : Float { return min(a.x, min(a.y, a.z)); }
|
|
|
|
|
|
function getPositionAt( uv: Vec2 ) : Vec3 {
|
|
|
var depth = depthMap.get(uv);
|
|
@@ -45,78 +45,78 @@ class LocalVolumetricShader extends hxsl.Shader {
|
|
|
return getPositionAt(screenUV);
|
|
|
}
|
|
|
|
|
|
- function rayBoxIntersection( o : Vec3, d : Vec3) : Vec3 {
|
|
|
- var m = 1.0/d;
|
|
|
- var n = m*o;
|
|
|
- var k = abs(m)*obH;
|
|
|
- var t1 = -n - k;
|
|
|
- var t2 = -n + k;
|
|
|
- var tN = maxComp(t1);
|
|
|
- var tF = minComp(t2);
|
|
|
- var hit = vec3(tN, tF, 1.0);
|
|
|
- if(tN>tF || tF<0.0) {
|
|
|
- hit = vec3(-1.0, -1.0, -1.0);
|
|
|
- }
|
|
|
- return hit;
|
|
|
- }
|
|
|
-
|
|
|
- function getPath(o : Vec3, d : Vec3) : Vec4 {
|
|
|
- var dir = normalize(d);
|
|
|
- var hit = rayBoxIntersection(o, dir);
|
|
|
- var path = vec4(o, -1.0);
|
|
|
- if(hit.z > 0.0){
|
|
|
- if(hit.x > 0.0){
|
|
|
- path = vec4(o+hit.x*dir, hit.y - hit.x);
|
|
|
- } else {
|
|
|
- path.w = hit.y;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- var backgroundLocalPosition = (vec4(getPosition(), 1.0) * global.modelViewInverse).xyz;
|
|
|
- var backgroundDist = length(backgroundLocalPosition-path.xyz);
|
|
|
- path.w = min(path.w, backgroundDist);
|
|
|
- return path;
|
|
|
- }
|
|
|
-
|
|
|
- function boxDistance(p : Vec3) : Float {
|
|
|
- var d = abs(p) - obH;
|
|
|
- return length(max(d,0.0)) + min(maxComp(d),0.0);
|
|
|
- }
|
|
|
-
|
|
|
- function boxDensity( o : Vec3, d : Vec3, t : Float) : Float{
|
|
|
- var ir2 = 1.0/(obH*obH);
|
|
|
- var a = 1.0 - (o*o)*ir2;
|
|
|
- var b = - 2.0*(o*d)*ir2;
|
|
|
- var c = - (d*d)*ir2;
|
|
|
-
|
|
|
- var t1 = t;
|
|
|
- var t2 = t1*t1;
|
|
|
- var t3 = t2*t1;
|
|
|
- var t4 = t2*t2;
|
|
|
- var t5 = t2*t3;
|
|
|
- var t6 = t3*t3;
|
|
|
- var t7 = t3*t4;
|
|
|
-
|
|
|
- var f = (t1/1.0) *(a.x*a.y*a.z) +
|
|
|
- (t2/2.0) *(a.x*a.y*b.z + a.x*b.y*a.z + b.x*a.y*a.z) +
|
|
|
- (t3/3.0) *(a.x*a.y*c.z + a.x*b.y*b.z + a.x*c.y*a.z + b.x*a.y*b.z + b.x*b.y*a.z + c.x*a.y*a.z) +
|
|
|
- (t4/4.0) *(a.x*b.y*c.z + a.x*c.y*b.z + b.x*a.y*c.z + b.x*b.y*b.z + b.x*c.y*a.z + c.x*a.y*b.z + c.x*b.y*a.z) +
|
|
|
- (t5/5.0) *(a.x*c.y*c.z + b.x*b.y*c.z + b.x*c.y*b.z + c.x*a.y*c.z + c.x*b.y*b.z + c.x*c.y*a.z) +
|
|
|
- (t6/6.0) *(b.x*c.y*c.z + c.x*b.y*c.z + c.x*c.y*b.z) +
|
|
|
- (t7/7.0) *(c.x*c.y*c.z);
|
|
|
-
|
|
|
- return f;
|
|
|
- }
|
|
|
-
|
|
|
- function sampleFog(pos : Vec3, dir : Vec3, dist : Float) : Float {
|
|
|
- return clamp(fogDensity * boxDensity(pos, dir, dist) / fogFade, 0.0, fogDensity);
|
|
|
- }
|
|
|
-
|
|
|
- function integrateBox(pos : Vec3, dir: Vec3, dist : Float, integrationValues : Vec4) : Vec4 {
|
|
|
- var extinction = sampleFog(pos, dir, dist/length(dir));
|
|
|
+ function rayBoxIntersection( o : Vec3, d : Vec3) : Vec3 {
|
|
|
+ var m = 1.0/d;
|
|
|
+ var n = m*o;
|
|
|
+ var k = abs(m)*obH;
|
|
|
+ var t1 = -n - k;
|
|
|
+ var t2 = -n + k;
|
|
|
+ var tN = maxComp(t1);
|
|
|
+ var tF = minComp(t2);
|
|
|
+ var hit = vec3(tN, tF, 1.0);
|
|
|
+ if(tN>tF || tF<0.0) {
|
|
|
+ hit = vec3(-1.0, -1.0, -1.0);
|
|
|
+ }
|
|
|
+ return hit;
|
|
|
+ }
|
|
|
+
|
|
|
+ function getPath(o : Vec3, d : Vec3) : Vec4 {
|
|
|
+ var dir = normalize(d);
|
|
|
+ var hit = rayBoxIntersection(o, dir);
|
|
|
+ var path = vec4(o, -1.0);
|
|
|
+ if(hit.z > 0.0){
|
|
|
+ if(hit.x > 0.0){
|
|
|
+ path = vec4(o+hit.x*dir, hit.y - hit.x);
|
|
|
+ } else {
|
|
|
+ path.w = hit.y;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var backgroundLocalPosition = (vec4(getPosition(), 1.0) * global.modelViewInverse).xyz;
|
|
|
+ var backgroundDist = length(backgroundLocalPosition-path.xyz);
|
|
|
+ path.w = min(path.w, backgroundDist);
|
|
|
+ return path;
|
|
|
+ }
|
|
|
+
|
|
|
+ function boxDistance(p : Vec3) : Float {
|
|
|
+ var d = abs(p) - obH;
|
|
|
+ return length(max(d,0.0)) + min(maxComp(d),0.0);
|
|
|
+ }
|
|
|
+
|
|
|
+ function boxDensity( o : Vec3, d : Vec3, t : Float) : Float{
|
|
|
+ var ir2 = 1.0/(obH*obH);
|
|
|
+ var a = 1.0 - (o*o)*ir2;
|
|
|
+ var b = - 2.0*(o*d)*ir2;
|
|
|
+ var c = - (d*d)*ir2;
|
|
|
+
|
|
|
+ var t1 = t;
|
|
|
+ var t2 = t1*t1;
|
|
|
+ var t3 = t2*t1;
|
|
|
+ var t4 = t2*t2;
|
|
|
+ var t5 = t2*t3;
|
|
|
+ var t6 = t3*t3;
|
|
|
+ var t7 = t3*t4;
|
|
|
+
|
|
|
+ var f = (t1/1.0) *(a.x*a.y*a.z) +
|
|
|
+ (t2/2.0) *(a.x*a.y*b.z + a.x*b.y*a.z + b.x*a.y*a.z) +
|
|
|
+ (t3/3.0) *(a.x*a.y*c.z + a.x*b.y*b.z + a.x*c.y*a.z + b.x*a.y*b.z + b.x*b.y*a.z + c.x*a.y*a.z) +
|
|
|
+ (t4/4.0) *(a.x*b.y*c.z + a.x*c.y*b.z + b.x*a.y*c.z + b.x*b.y*b.z + b.x*c.y*a.z + c.x*a.y*b.z + c.x*b.y*a.z) +
|
|
|
+ (t5/5.0) *(a.x*c.y*c.z + b.x*b.y*c.z + b.x*c.y*b.z + c.x*a.y*c.z + c.x*b.y*b.z + c.x*c.y*a.z) +
|
|
|
+ (t6/6.0) *(b.x*c.y*c.z + c.x*b.y*c.z + c.x*c.y*b.z) +
|
|
|
+ (t7/7.0) *(c.x*c.y*c.z);
|
|
|
+
|
|
|
+ return f;
|
|
|
+ }
|
|
|
+
|
|
|
+ function sampleFog(pos : Vec3, dir : Vec3, dist : Float) : Float {
|
|
|
+ return clamp(fogDensity * boxDensity(pos, dir, dist) / fogFade, 0.0, fogDensity);
|
|
|
+ }
|
|
|
+
|
|
|
+ function integrateBox(pos : Vec3, dir: Vec3, dist : Float, integrationValues : Vec4) : Vec4 {
|
|
|
+ var extinction = sampleFog(pos, dir, dist/length(dir));
|
|
|
var clampedExtinction = max(extinction, 1e-5);
|
|
|
var transmittance = exp(-extinction*dist);
|
|
|
- var integScatt = fogColor.rgb;
|
|
|
+ var integScatt = fogColor.rgb;
|
|
|
|
|
|
integrationValues.rgb += integrationValues.a * integScatt;
|
|
|
integrationValues.a *= transmittance;
|
|
@@ -124,129 +124,129 @@ class LocalVolumetricShader extends hxsl.Shader {
|
|
|
return integrationValues;
|
|
|
}
|
|
|
|
|
|
- function evaluate() : Vec4 {
|
|
|
- var dir = normalize(transformedPosition - camera.position);
|
|
|
- var pos = camera.position;
|
|
|
+ function evaluate() : Vec4 {
|
|
|
+ var dir = normalize(transformedPosition - camera.position);
|
|
|
+ var pos = camera.position;
|
|
|
|
|
|
- dir = dir * global.modelViewInverse.mat3();
|
|
|
- pos = (vec4(pos, 1) * global.modelViewInverse).xyz;
|
|
|
+ dir = dir * global.modelViewInverse.mat3();
|
|
|
+ pos = (vec4(pos, 1) * global.modelViewInverse).xyz;
|
|
|
|
|
|
- var path = getPath(pos, dir);
|
|
|
+ var path = getPath(pos, dir);
|
|
|
|
|
|
- var integrationValues = vec4(0.0,0.0,0.0,1.0);
|
|
|
- return integrateBox(path.xyz, dir, path.w, integrationValues);
|
|
|
- }
|
|
|
+ var integrationValues = vec4(0.0,0.0,0.0,1.0);
|
|
|
+ return integrateBox(path.xyz, dir, path.w, integrationValues);
|
|
|
+ }
|
|
|
|
|
|
function fragment() {
|
|
|
- var volumetric = evaluate();
|
|
|
- volumetric.a = saturate(1.0 - volumetric.a);
|
|
|
- volumetric.a = volumetric.a > 1.0 - 1e-3 ? 1.0 : volumetric.a;
|
|
|
- pixelColor = volumetric;
|
|
|
+ var volumetric = evaluate();
|
|
|
+ volumetric.a = saturate(1.0 - volumetric.a);
|
|
|
+ volumetric.a = volumetric.a > 1.0 - 1e-3 ? 1.0 : volumetric.a;
|
|
|
+ pixelColor = volumetric;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
class LocalVolumetricLightingObject extends h3d.scene.Object {
|
|
|
|
|
|
- public var localVolume : LocalVolumetricLighting;
|
|
|
- public var bounds : h3d.col.OrientedBounds;
|
|
|
-
|
|
|
- public var mesh : h3d.scene.Mesh;
|
|
|
- public var boundsDisplay : h3d.scene.Graphics;
|
|
|
-
|
|
|
- public var shader : LocalVolumetricShader;
|
|
|
-
|
|
|
- public function new(parent:h3d.scene.Object, localVolume:LocalVolumetricLighting) {
|
|
|
- this.localVolume = localVolume;
|
|
|
- super(parent);
|
|
|
- bounds = new h3d.col.OrientedBounds();
|
|
|
-
|
|
|
- var prim = new h3d.prim.Cube(1,1,1,true);
|
|
|
- prim.addNormals();
|
|
|
- mesh = new h3d.scene.Mesh(prim, this);
|
|
|
-
|
|
|
- var material = mesh.material;
|
|
|
- material.castShadows = false;
|
|
|
- mesh.material.mainPass.setPassName("volumetricOverlay");
|
|
|
- mesh.material.mainPass.setBlendMode(h3d.mat.BlendMode.Alpha);
|
|
|
-
|
|
|
- shader = new LocalVolumetricShader();
|
|
|
-
|
|
|
- material.mainPass.addShader(shader);
|
|
|
-
|
|
|
- refresh();
|
|
|
- }
|
|
|
-
|
|
|
- function isInside(ctx : h3d.scene.RenderContext) : Bool {
|
|
|
- var pos = ctx.camera.pos.clone().add(ctx.camera.getForward().scaled(ctx.camera.zNear));
|
|
|
- pos.transform(this.getInvPos());
|
|
|
- return pos.x >= -bounds.hx && pos.x <= bounds.hx &&
|
|
|
- pos.y >= -bounds.hy && pos.y <= bounds.hy &&
|
|
|
- pos.z >= -bounds.hz && pos.z <= bounds.hz;
|
|
|
- }
|
|
|
-
|
|
|
- override function sync(ctx : h3d.scene.RenderContext) {
|
|
|
- var inside = isInside(ctx);
|
|
|
- if(inside){
|
|
|
- mesh.material.mainPass.culling = Front;
|
|
|
- mesh.material.mainPass.depthTest = Always;
|
|
|
- } else{
|
|
|
- mesh.material.mainPass.culling = Back;
|
|
|
- mesh.material.mainPass.depthTest = Less;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public function refresh() {
|
|
|
- var ob = bounds;
|
|
|
- shader.obH.set(ob.hx, ob.hy, ob.hz);
|
|
|
-
|
|
|
- shader.fogColor.setColor(localVolume.color);
|
|
|
- shader.fogDensity = localVolume.fogDensity;
|
|
|
- shader.fogFade = localVolume.fogFade;
|
|
|
-
|
|
|
- if(localVolume.showBounds){
|
|
|
- boundsDisplay = bounds.makeDebugObj();
|
|
|
- boundsDisplay.lineStyle(2, 0xFFFFFF);
|
|
|
- addChild(boundsDisplay);
|
|
|
- } else if(boundsDisplay != null){
|
|
|
- removeChild(boundsDisplay);
|
|
|
- boundsDisplay = null;
|
|
|
- }
|
|
|
- }
|
|
|
+ public var localVolume : LocalVolumetricLighting;
|
|
|
+ public var bounds : h3d.col.OrientedBounds;
|
|
|
+
|
|
|
+ public var mesh : h3d.scene.Mesh;
|
|
|
+ public var boundsDisplay : h3d.scene.Graphics;
|
|
|
+
|
|
|
+ public var shader : LocalVolumetricShader;
|
|
|
+
|
|
|
+ public function new(parent:h3d.scene.Object, localVolume:LocalVolumetricLighting) {
|
|
|
+ this.localVolume = localVolume;
|
|
|
+ super(parent);
|
|
|
+ bounds = new h3d.col.OrientedBounds();
|
|
|
+
|
|
|
+ var prim = new h3d.prim.Cube(1,1,1,true);
|
|
|
+ prim.addNormals();
|
|
|
+ mesh = new h3d.scene.Mesh(prim, this);
|
|
|
+
|
|
|
+ var material = mesh.material;
|
|
|
+ material.castShadows = false;
|
|
|
+ mesh.material.mainPass.setPassName("volumetricOverlay");
|
|
|
+ mesh.material.mainPass.setBlendMode(h3d.mat.BlendMode.Alpha);
|
|
|
+
|
|
|
+ shader = new LocalVolumetricShader();
|
|
|
+
|
|
|
+ material.mainPass.addShader(shader);
|
|
|
+
|
|
|
+ refresh();
|
|
|
+ }
|
|
|
+
|
|
|
+ function isInside(ctx : h3d.scene.RenderContext) : Bool {
|
|
|
+ var pos = ctx.camera.pos.clone().add(ctx.camera.getForward().scaled(ctx.camera.zNear));
|
|
|
+ pos.transform(this.getInvPos());
|
|
|
+ return pos.x >= -bounds.hx && pos.x <= bounds.hx &&
|
|
|
+ pos.y >= -bounds.hy && pos.y <= bounds.hy &&
|
|
|
+ pos.z >= -bounds.hz && pos.z <= bounds.hz;
|
|
|
+ }
|
|
|
+
|
|
|
+ override function sync(ctx : h3d.scene.RenderContext) {
|
|
|
+ var inside = isInside(ctx);
|
|
|
+ if(inside){
|
|
|
+ mesh.material.mainPass.culling = Front;
|
|
|
+ mesh.material.mainPass.depthTest = Always;
|
|
|
+ } else{
|
|
|
+ mesh.material.mainPass.culling = Back;
|
|
|
+ mesh.material.mainPass.depthTest = Less;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public function refresh() {
|
|
|
+ var ob = bounds;
|
|
|
+ shader.obH.set(ob.hx, ob.hy, ob.hz);
|
|
|
+
|
|
|
+ shader.fogColor.setColor(localVolume.color);
|
|
|
+ shader.fogDensity = localVolume.fogDensity;
|
|
|
+ shader.fogFade = localVolume.fogFade;
|
|
|
+
|
|
|
+ if(localVolume.showBounds){
|
|
|
+ boundsDisplay = bounds.makeDebugObj();
|
|
|
+ boundsDisplay.lineStyle(2, 0xFFFFFF);
|
|
|
+ addChild(boundsDisplay);
|
|
|
+ } else if(boundsDisplay != null){
|
|
|
+ removeChild(boundsDisplay);
|
|
|
+ boundsDisplay = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
class LocalVolumetricLighting extends hrt.prefab.Object3D {
|
|
|
|
|
|
- var localVolumeObject : LocalVolumetricLightingObject;
|
|
|
+ var localVolumeObject : LocalVolumetricLightingObject;
|
|
|
|
|
|
- @:s public var fogDensity : Float = 1.0;
|
|
|
- @:s public var fogFade : Float = 1.0;
|
|
|
- @:s public var color : Int = 0xFFFFFF;
|
|
|
+ @:s public var fogDensity : Float = 1.0;
|
|
|
+ @:s public var fogFade : Float = 1.0;
|
|
|
+ @:s public var color : Int = 0xFFFFFF;
|
|
|
|
|
|
- @:s public var showBounds : Bool = false;
|
|
|
+ @:s public var showBounds : Bool = false;
|
|
|
|
|
|
- override function makeObject(parent3d: h3d.scene.Object) : h3d.scene.Object {
|
|
|
- localVolumeObject = new LocalVolumetricLightingObject(parent3d, this);
|
|
|
- return localVolumeObject;
|
|
|
+ override function makeObject(parent3d: h3d.scene.Object) : h3d.scene.Object {
|
|
|
+ localVolumeObject = new LocalVolumetricLightingObject(parent3d, this);
|
|
|
+ return localVolumeObject;
|
|
|
}
|
|
|
|
|
|
override function updateInstance(?propName) {
|
|
|
- super.updateInstance(propName);
|
|
|
+ super.updateInstance(propName);
|
|
|
localVolumeObject.refresh();
|
|
|
}
|
|
|
|
|
|
#if editor
|
|
|
override function edit( ctx : hide.prefab.EditContext ) {
|
|
|
- super.edit(ctx);
|
|
|
+ super.edit(ctx);
|
|
|
ctx.properties.add(new hide.Element('
|
|
|
<div class="group" name="Rendering">
|
|
|
<dl>
|
|
|
- <dt>Density</dt><dd><input type="range" min="0" max="2" field="fogDensity"/></dd>
|
|
|
- <dt>Fade</dt><dd><input type="range" min="0" max="1" field="fogFade"/></dd>
|
|
|
+ <dt>Density</dt><dd><input type="range" min="0" max="2" field="fogDensity"/></dd>
|
|
|
+ <dt>Fade</dt><dd><input type="range" min="0" max="1" field="fogFade"/></dd>
|
|
|
<dt>Color</dt><dd><input type="color" field="color"/></dd>
|
|
|
</dl>
|
|
|
</div>
|
|
|
- <div class="group" name="Debug">
|
|
|
+ <div class="group" name="Debug">
|
|
|
<dl>
|
|
|
<dt>Show Bounds</dt><dd><input type="checkbox" field="showBounds"/></dd>
|
|
|
</dl>
|
|
@@ -255,5 +255,5 @@ class LocalVolumetricLighting extends hrt.prefab.Object3D {
|
|
|
}
|
|
|
#end
|
|
|
|
|
|
- static var _ = hrt.prefab.Prefab.register("LocalVolumeLighting", LocalVolumetricLighting);
|
|
|
-}
|
|
|
+ static var _ = hrt.prefab.Prefab.register("LocalVolumeLighting", LocalVolumetricLighting);
|
|
|
+}
|