Prefilter envmap
This commit is contained in:
@@ -9,10 +9,10 @@ out vec4 fragColor;
|
||||
|
||||
const float PI = 3.14159265358979;
|
||||
const float PI2 = PI * 2.0;
|
||||
const int samples = 32;
|
||||
const int samples = 1024 * 16;
|
||||
|
||||
float rand(const vec2 co) {
|
||||
return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453);
|
||||
float rand(vec2 co) {
|
||||
return fract(sin(mod(dot(co.xy, vec2(12.9898, 78.233)), 3.14)) * 43758.5453);
|
||||
}
|
||||
|
||||
vec2 equirect(vec3 normal) {
|
||||
@@ -22,18 +22,18 @@ vec2 equirect(vec3 normal) {
|
||||
}
|
||||
|
||||
vec3 reverseEquirect(vec2 co) {
|
||||
float theta = co.x * PI2;
|
||||
float theta = co.x * PI2 - PI;
|
||||
float phi = co.y * PI;
|
||||
return vec3(cos(theta), -sin(theta), cos(phi));
|
||||
return vec3(sin(phi) * cos(theta), -(sin(phi) * sin(theta)), cos(phi));
|
||||
}
|
||||
|
||||
vec3 cos_weighted_hemisphere_direction(vec3 n, vec2 co, uint seed) {
|
||||
vec2 r = vec2(rand(co * seed), rand(co * 2 * seed));
|
||||
vec2 r = vec2(rand(co * seed), rand(co * seed * 2));
|
||||
vec3 uu = normalize(cross(n, vec3(0.0, 1.0, 1.0)));
|
||||
vec3 vv = cross(uu, n);
|
||||
float ra = sqrt(r.y);
|
||||
float rx = ra * cos(6.2831 * r.x);
|
||||
float ry = ra * sin(6.2831 * r.x);
|
||||
float rx = ra * cos(PI2 * r.x);
|
||||
float ry = ra * sin(PI2 * r.x);
|
||||
float rz = sqrt(1.0 - r.y);
|
||||
vec3 rr = vec3(rx * uu + ry * vv + rz * n);
|
||||
return normalize(rr);
|
||||
@@ -43,8 +43,9 @@ void main() {
|
||||
fragColor = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
vec3 n = reverseEquirect(texCoord);
|
||||
for (int i = 0; i < samples; i++) {
|
||||
vec3 dir = mix(n, cos_weighted_hemisphere_direction(n, texCoord, i), params.x / 32.0);
|
||||
vec3 dir = normalize(mix(n, cos_weighted_hemisphere_direction(n, texCoord, i), params.x));
|
||||
fragColor.rgb += texture(radiance, equirect(dir)).rgb;
|
||||
}
|
||||
fragColor.rgb /= float(samples);
|
||||
fragColor.rgb = pow(fragColor.rgb, vec3(1.0 / 2.2));
|
||||
}
|
||||
|
||||
@@ -22,14 +22,6 @@ float rand(const vec2 co) { // Unreliable
|
||||
return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453);
|
||||
}
|
||||
|
||||
vec2 rand2(const vec2 coord) {
|
||||
const float width = 1100;
|
||||
const float height = 500;
|
||||
float noiseX = ((fract(1.0 - coord.s * (width / 2.0)) * 0.25) + (fract(coord.t * (height / 2.0)) * 0.75)) * 2.0 - 1.0;
|
||||
float noiseY = ((fract(1.0 - coord.s * (width / 2.0)) * 0.75) + (fract(coord.t * (height / 2.0)) * 0.25)) * 2.0 - 1.0;
|
||||
return vec2(noiseX, noiseY);
|
||||
}
|
||||
|
||||
float linearize(const float depth, vec2 cameraProj) {
|
||||
// to viewz
|
||||
return cameraProj.y / (depth - cameraProj.x);
|
||||
|
||||
@@ -8,9 +8,13 @@ import kha.graphics4.VertexStructure;
|
||||
import kha.graphics4.VertexData;
|
||||
import kha.graphics4.TextureUnit;
|
||||
import kha.graphics4.ConstantLocation;
|
||||
import kha.graphics4.TextureAddressing;
|
||||
import kha.graphics4.TextureFilter;
|
||||
import kha.graphics4.MipMapFilter;
|
||||
import kha.arrays.Float32Array;
|
||||
import iron.data.Data;
|
||||
import iron.data.ConstData;
|
||||
import iron.math.Vec4;
|
||||
import iron.Scene;
|
||||
import arm.sys.Path;
|
||||
|
||||
@@ -18,10 +22,13 @@ class ImportEnvmap {
|
||||
|
||||
static var pipeline: PipelineState = null;
|
||||
static var paramsLocation: ConstantLocation;
|
||||
static var params = new iron.math.Vec4();
|
||||
static var params = new Vec4();
|
||||
static var n = new Vec4();
|
||||
static var radianceLocation: TextureUnit;
|
||||
static var radiance: Image = null;
|
||||
static var radianceCpu: Image = null;
|
||||
static var mips: Array<Image> = null;
|
||||
static var mipsCpu: Array<Image> = null;
|
||||
|
||||
public static function run(path: String, image: Image) {
|
||||
|
||||
@@ -33,46 +40,54 @@ class ImportEnvmap {
|
||||
var vs = new VertexStructure();
|
||||
vs.add("pos", VertexData.Float2);
|
||||
pipeline.inputLayout = [vs];
|
||||
pipeline.colorAttachmentCount = 1;
|
||||
pipeline.colorAttachments[0] = TextureFormat.RGBA128;
|
||||
pipeline.compile();
|
||||
paramsLocation = pipeline.getConstantLocation("params");
|
||||
radianceLocation = pipeline.getTextureUnit("radiance");
|
||||
|
||||
radiance = Image.createRenderTarget(512, 256, TextureFormat.RGBA64);
|
||||
radiance = Image.createRenderTarget(1024, 512, TextureFormat.RGBA128);
|
||||
|
||||
mips = [];
|
||||
var w = 256;
|
||||
for (i in 0...9) {
|
||||
mips.push(Image.createRenderTarget(w, w > 1 ? Std.int(w / 2) : 1, TextureFormat.RGBA64));
|
||||
var w = 512;
|
||||
for (i in 0...10) {
|
||||
mips.push(Image.createRenderTarget(w, w > 1 ? Std.int(w / 2) : 1, TextureFormat.RGBA128));
|
||||
w = Std.int(w / 2);
|
||||
}
|
||||
|
||||
if (ConstData.screenAlignedVB == null) ConstData.createScreenAlignedData();
|
||||
}
|
||||
|
||||
// Down-scale to 512x256
|
||||
// Down-scale to 1024x512
|
||||
radiance.g2.begin(false);
|
||||
radiance.g2.drawImage(image, 0, 0);
|
||||
radiance.g2.pipeline = Layers.pipeCopy128;
|
||||
radiance.g2.drawScaledImage(image, 0, 0, 1024, 512);
|
||||
radiance.g2.pipeline = null;
|
||||
radiance.g2.end();
|
||||
|
||||
var radianceCpu = Image.fromBytes(radiance.getPixels(), radiance.width, radiance.height, TextureFormat.RGBA64, kha.graphics4.Usage.DynamicUsage);
|
||||
var radiancePixels = radiance.getPixels();
|
||||
if (radianceCpu != null) radianceCpu.unload();
|
||||
radianceCpu = Image.fromBytes(radiancePixels, radiance.width, radiance.height, TextureFormat.RGBA128, kha.graphics4.Usage.DynamicUsage);
|
||||
|
||||
// Radiance
|
||||
var mipsCpu: Array<Image> = [];
|
||||
if (mipsCpu != null) for (mip in mipsCpu) mip.unload();
|
||||
mipsCpu = [];
|
||||
for (i in 0...mips.length) {
|
||||
getRadianceMip(mips[i], i, radiance);
|
||||
mipsCpu.push(Image.fromBytes(mips[i].getPixels(), mips[i].width, mips[i].height, TextureFormat.RGBA64, kha.graphics4.Usage.DynamicUsage));
|
||||
mipsCpu.push(Image.fromBytes(mips[i].getPixels(), mips[i].width, mips[i].height, TextureFormat.RGBA128, kha.graphics4.Usage.DynamicUsage));
|
||||
}
|
||||
radianceCpu.setMipmaps(mipsCpu);
|
||||
|
||||
// Irradiance
|
||||
Scene.active.world.probe.irradiance = getSphericalHarmonics(radiance);
|
||||
Scene.active.world.probe.irradiance = getSphericalHarmonics(radiancePixels, radiance.width, radiance.height);
|
||||
|
||||
// World
|
||||
Scene.active.world.probe.raw.strength = 1.0;
|
||||
Scene.active.world.probe.raw.radiance_mipmaps = mipsCpu.length - 2;
|
||||
Scene.active.world.envmap = image;
|
||||
Scene.active.world.raw.envmap = path;
|
||||
Scene.active.world.probe.radiance = radianceCpu;
|
||||
Scene.active.world.probe.radianceMipmaps = mips;
|
||||
Scene.active.world.probe.radianceMipmaps = mipsCpu;
|
||||
Context.savedEnvmap = image;
|
||||
Context.showEnvmapHandle.selected = Context.showEnvmap = true;
|
||||
if (Context.showEnvmapBlur) {
|
||||
@@ -86,15 +101,61 @@ class ImportEnvmap {
|
||||
mip.g4.setVertexBuffer(ConstData.screenAlignedVB);
|
||||
mip.g4.setIndexBuffer(ConstData.screenAlignedIB);
|
||||
mip.g4.setPipeline(pipeline);
|
||||
params.x = level;
|
||||
params.x = 0.1 + level / 8;
|
||||
mip.g4.setFloat4(paramsLocation, params.x, params.y, params.z, params.w);
|
||||
mip.g4.setTexture(radianceLocation, radiance);
|
||||
mip.g4.drawIndexedVertices();
|
||||
mip.g4.end();
|
||||
}
|
||||
|
||||
static function getSphericalHarmonics(source: kha.Image) {
|
||||
static function reverseEquirect(x: Float, y: Float): Vec4 {
|
||||
var theta = x * Math.PI * 2 - Math.PI;
|
||||
var phi = y * Math.PI;
|
||||
// return n.set(Math.sin(phi) * Math.cos(theta), -(Math.sin(phi) * Math.sin(theta)), Math.cos(phi));
|
||||
return n.set(-Math.cos(phi), Math.sin(phi) * Math.cos(theta), -(Math.sin(phi) * Math.sin(theta)));
|
||||
}
|
||||
|
||||
// https://ndotl.wordpress.com/2015/03/07/pbr-cubemap-filtering
|
||||
// https://seblagarde.wordpress.com/2012/06/10/amd-cubemapgen-for-physically-based-rendering
|
||||
static function getSphericalHarmonics(source: haxe.io.Bytes, sourceWidth: Int, sourceHeight: Int): Float32Array {
|
||||
var sh = new Float32Array(9 * 3 + 1); // Align to mult of 4 - 27->28
|
||||
var accum = 0.0;
|
||||
var weight = 1.0;
|
||||
var weight1 = weight * 4 / 17;
|
||||
var weight2 = weight * 8 / 17;
|
||||
var weight3 = weight * 15 / 17;
|
||||
var weight4 = weight * 5 / 68;
|
||||
var weight5 = weight * 15 / 68;
|
||||
|
||||
for (x in 0...sourceWidth) {
|
||||
for (y in 0...sourceHeight) {
|
||||
n = reverseEquirect(x / sourceWidth, y / sourceHeight);
|
||||
|
||||
for (i in 0...3) {
|
||||
var value = source.getFloat(((x + y * sourceWidth) * 16 + i * 4));
|
||||
value = Math.pow(value, 1.0 / 2.2);
|
||||
|
||||
sh[0 + i] += value * weight1;
|
||||
sh[3 + i] += value * weight2 * n.x;
|
||||
sh[6 + i] += value * weight2 * n.y;
|
||||
sh[9 + i] += value * weight2 * n.z;
|
||||
|
||||
sh[12 + i] += value * weight3 * n.x * n.z;
|
||||
sh[15 + i] += value * weight3 * n.z * n.y;
|
||||
sh[18 + i] += value * weight3 * n.y * n.x;
|
||||
|
||||
sh[21 + i] += value * weight4 * (3.0 * n.z * n.z - 1.0);
|
||||
sh[24 + i] += value * weight5 * (n.x * n.x - n.y * n.y);
|
||||
|
||||
accum += weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i in 0...sh.length) {
|
||||
sh[i] /= accum / 16;
|
||||
}
|
||||
|
||||
return sh;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,18 @@ class ImportTexture {
|
||||
return;
|
||||
}
|
||||
|
||||
for (a in Project.assets) if (a.file == path) { Log.info(Strings.info0); return; }
|
||||
for (a in Project.assets) {
|
||||
if (a.file == path) {
|
||||
// Set envmap
|
||||
if (path.toLowerCase().endsWith(".hdr")) {
|
||||
Data.getImage(path, function(image: kha.Image) {
|
||||
ImportEnvmap.run(path, image);
|
||||
});
|
||||
}
|
||||
Log.info(Strings.info0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var ext = path.substr(path.lastIndexOf(".") + 1);
|
||||
var importer = Path.textureImporters.get(ext);
|
||||
|
||||
Reference in New Issue
Block a user