diff --git a/Shaders/common/prefilter_envmap.frag.glsl b/Shaders/common/prefilter_envmap.frag.glsl index deeaf276..404f2cfa 100644 --- a/Shaders/common/prefilter_envmap.frag.glsl +++ b/Shaders/common/prefilter_envmap.frag.glsl @@ -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)); } diff --git a/Shaders/std/math.glsl b/Shaders/std/math.glsl index 94f83cfe..b684693f 100644 --- a/Shaders/std/math.glsl +++ b/Shaders/std/math.glsl @@ -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); diff --git a/Sources/arm/io/ImportEnvmap.hx b/Sources/arm/io/ImportEnvmap.hx index 7bb44673..7ab95ffd 100644 --- a/Sources/arm/io/ImportEnvmap.hx +++ b/Sources/arm/io/ImportEnvmap.hx @@ -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 = null; + static var mipsCpu: Array = 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 = []; + 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; } } diff --git a/Sources/arm/io/ImportTexture.hx b/Sources/arm/io/ImportTexture.hx index 92106139..8efe6d97 100644 --- a/Sources/arm/io/ImportTexture.hx +++ b/Sources/arm/io/ImportTexture.hx @@ -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);