diff --git a/Sources/Main.hx b/Sources/Main.hx index 52299749..e66e4454 100644 --- a/Sources/Main.hx +++ b/Sources/Main.hx @@ -8,10 +8,12 @@ import iron.object.Object; import iron.Scene; import iron.RenderPath; import arm.render.Inc; +import arm.render.RenderPathForward; import arm.render.RenderPathDeferred; import arm.render.Uniforms; import arm.util.BuildMacros; import arm.Config; +import arm.Context; #if arm_player import arm.sys.Path; #end @@ -80,8 +82,17 @@ class Main { Uniforms.init(); var path = new RenderPath(); Inc.init(path); - RenderPathDeferred.init(path); - path.commands = RenderPathDeferred.commands; + + if (Context.renderMode == RenderForward) { + RenderPathDeferred.init(path); // Allocate gbuffer + RenderPathForward.init(path); + path.commands = RenderPathForward.commands; + } + else { + RenderPathDeferred.init(path); + path.commands = RenderPathDeferred.commands; + } + RenderPath.setActive(path); #if arm_player new arm.Player(); diff --git a/Sources/arm/Context.hx b/Sources/arm/Context.hx index b9607d2b..d78c9d0b 100644 --- a/Sources/arm/Context.hx +++ b/Sources/arm/Context.hx @@ -98,7 +98,12 @@ class Context { #end public static var decalImage: Image = null; public static var decalPreview = false; - public static var viewportMode = ViewRender; + public static var viewportMode = ViewLit; + #if (krom_android || krom_ios) + public static var renderMode = RenderForward; + #else + public static var renderMode = RenderDeferred; + #end public static var hscaleWasChanged = false; public static var exportMeshFormat = FormatObj; #if (krom_android || krom_ios) diff --git a/Sources/arm/Enums.hx b/Sources/arm/Enums.hx index 99cb4018..00b2a17d 100644 --- a/Sources/arm/Enums.hx +++ b/Sources/arm/Enums.hx @@ -65,7 +65,7 @@ package arm; } @:enum abstract ViewportMode(Int) from Int to Int { - var ViewRender = 0; + var ViewLit = 0; var ViewBaseColor = 1; var ViewNormalMap = 2; var ViewOcclusion = 3; @@ -80,6 +80,12 @@ package arm; var ViewPathTrace = 12; } +@:enum abstract RenderMode(Int) from Int to Int { + var RenderDeferred = 0; + var RenderForward = 1; + var RenderPathTrace = 2; +} + @:enum abstract FillType(Int) from Int to Int { var FillObject = 0; var FillFace = 1; diff --git a/Sources/arm/node/MakeMesh.hx b/Sources/arm/node/MakeMesh.hx index 27a40ecc..a3a31354 100644 --- a/Sources/arm/node/MakeMesh.hx +++ b/Sources/arm/node/MakeMesh.hx @@ -101,7 +101,7 @@ class MakeMesh { frag.add_shared_sampler('sampler2D texpaint'); frag.write('vec4 texpaint_sample = textureLodShared(texpaint, texCoord, 0.0);'); #if kha_direct3d12 - if (Context.viewportMode == ViewRender) { + if (Context.viewportMode == ViewLit) { frag.write('if (texpaint_sample.a < 0.1) discard;'); } #end @@ -328,15 +328,69 @@ class MakeMesh { // frag.write('if (basecol == vec3(0,0,0)) discard;'); } + if (Context.renderMode == RenderForward) { + frag.write('vec3 wn = n;'); + } + frag.write('n /= (abs(n.x) + abs(n.y) + abs(n.z));'); frag.write('n.xy = n.z >= 0.0 ? n.xy : octahedronWrap(n.xy);'); frag.write('basecol = pow(basecol, vec3(2.2, 2.2, 2.2));'); frag.write('fragColor[0] = vec4(n.xy, roughness, packFloatInt16(metallic, uint(matid)));'); - var deferred = Context.viewportMode == ViewRender || Context.viewportMode == ViewPathTrace; - if (deferred) { - if (MaterialBuilder.emisUsed) frag.write('if (matid == 1.0) basecol *= 10.0;'); // Boost for bloom - frag.write('fragColor[1] = vec4(basecol, packFloat2(occlusion, 1.0));'); // occ/spec + if (Context.viewportMode == ViewLit || Context.viewportMode == ViewPathTrace) { + if (Context.renderMode == RenderForward) { + frag.wposition = true; + frag.write('vec3 albedo = mix(basecol, vec3(0.0, 0.0, 0.0), metallic);'); + frag.write('vec3 f0 = mix(vec3(0.04, 0.04, 0.04), basecol, metallic);'); + frag.write('float dotNV = max(dot(wn, vVec), 0.0);'); + frag.add_uniform('sampler2D senvmapBrdf', "$brdf.k"); + frag.write('vec2 envBRDF = texture(senvmapBrdf, vec2(roughness, 1.0 - dotNV)).xy;'); + frag.add_uniform('sampler2D senvmapRadiance', '_envmapRadiance'); + frag.add_uniform('int envmapNumMipmaps', '_envmapNumMipmaps'); + frag.write('vec3 wreflect = reflect(-vVec, wn);'); + frag.write('float envlod = roughness * envmapNumMipmaps;'); + frag.add_function(MaterialFunctions.str_envMapEquirect); + frag.write('vec3 prefilteredColor = textureLod(senvmapRadiance, envMapEquirect(wreflect), envlod).rgb;'); + + frag.add_uniform('vec3 lightArea0', '_lightArea0'); + frag.add_uniform('vec3 lightArea1', '_lightArea1'); + frag.add_uniform('vec3 lightArea2', '_lightArea2'); + frag.add_uniform('vec3 lightArea3', '_lightArea3'); + frag.add_uniform('sampler2D sltcMat', '_ltcMat'); + frag.add_uniform('sampler2D sltcMag', '_ltcMag'); + frag.add_function(MaterialFunctions.str_ltcEvaluate); + frag.add_uniform('vec3 lightPos', '_pointPosition'); + frag.add_uniform('vec3 lightColor', '_pointColor'); + // frag.write('float dotNL = max(dot(wn, normalize(lightPos - wposition)), 0.0);'); + // frag.write('vec3 direct = albedo * dotNL;'); + frag.write('float ldist = distance(wposition, lightPos);'); + frag.write('const float LUT_SIZE = 64.0;'); + frag.write('const float LUT_SCALE = (LUT_SIZE - 1.0) / LUT_SIZE;'); + frag.write('const float LUT_BIAS = 0.5 / LUT_SIZE;'); + frag.write('float theta = acos(dotNV);'); + frag.write('vec2 tuv = vec2(roughness, theta / (0.5 * 3.14159265));'); + frag.write('tuv = tuv * LUT_SCALE + LUT_BIAS;'); + frag.write('vec4 t = textureLod(sltcMat, tuv, 0.0);'); + frag.write('mat3 minv = mat3(vec3(1.0, 0.0, t.y), vec3(0.0, t.z, 0.0), vec3(t.w, 0.0, t.x));'); + frag.write('float ltcspec = ltcEvaluate(wn, vVec, dotNV, wposition, minv, lightArea0, lightArea1, lightArea2, lightArea3);'); + frag.write('ltcspec *= textureLod(sltcMag, tuv, 0.0).a;'); + frag.write('mat3 mident = mat3(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0);'); + frag.write('float ltcdiff = ltcEvaluate(wn, vVec, dotNV, wposition, mident, lightArea0, lightArea1, lightArea2, lightArea3);'); + frag.write('vec3 direct = albedo * ltcdiff + ltcspec * 0.05;'); + frag.write('direct *= lightColor * (1.0 / (ldist * ldist));'); + + frag.add_uniform('float envmapStrength', '_envmapStrength'); + frag.add_uniform('vec4 shirr[7]', '_envmapIrradiance'); + frag.add_function(MaterialFunctions.str_shIrradiance); + frag.write('vec3 indirect = albedo * (shIrradiance(wn) / 3.14159265);'); + frag.write('indirect += prefilteredColor * (f0 * envBRDF.x + envBRDF.y) * 1.5;'); + frag.write('indirect *= envmapStrength * occlusion;'); + frag.write('fragColor[1] = vec4(direct + indirect, 1.0);'); + } + else { // Deferred, Pathtraced + if (MaterialBuilder.emisUsed) frag.write('if (matid == 1.0) basecol *= 10.0;'); // Boost for bloom + frag.write('fragColor[1] = vec4(basecol, packFloat2(occlusion, 1.0));'); // occ/spec + } } else if (Context.viewportMode == ViewBaseColor) { frag.write('fragColor[1] = vec4(basecol, 1.0);'); diff --git a/Sources/arm/node/Material.hx b/Sources/arm/node/Material.hx index 7f99afd0..91860b27 100644 --- a/Sources/arm/node/Material.hx +++ b/Sources/arm/node/Material.hx @@ -1254,7 +1254,7 @@ class Material { out_val = 'atan($val1)'; } else if (op == "ARCTAN2") { - out_val = 'atan2($val1, $val2)'; + out_val = 'atan($val2, $val1)'; } if (use_clamp) { return 'clamp($out_val, 0.0, 1.0)'; diff --git a/Sources/arm/node/MaterialFunctions.hx b/Sources/arm/node/MaterialFunctions.hx index 040acae9..6c3c2cc1 100644 --- a/Sources/arm/node/MaterialFunctions.hx +++ b/Sources/arm/node/MaterialFunctions.hx @@ -311,6 +311,169 @@ void createBasis(vec3 normal, out vec3 tangent, out vec3 binormal) { tangent = normalize(cameraRight - normal * dot(cameraRight, normal)); binormal = cross(tangent, normal); } +"; + + public static var str_shIrradiance = " +vec3 shIrradiance(const vec3 nor) { + const float c1 = 0.429043; + const float c2 = 0.511664; + const float c3 = 0.743125; + const float c4 = 0.886227; + const float c5 = 0.247708; + vec3 cl00 = vec3(shirr[0].x, shirr[0].y, shirr[0].z); + vec3 cl1m1 = vec3(shirr[0].w, shirr[1].x, shirr[1].y); + vec3 cl10 = vec3(shirr[1].z, shirr[1].w, shirr[2].x); + vec3 cl11 = vec3(shirr[2].y, shirr[2].z, shirr[2].w); + vec3 cl2m2 = vec3(shirr[3].x, shirr[3].y, shirr[3].z); + vec3 cl2m1 = vec3(shirr[3].w, shirr[4].x, shirr[4].y); + vec3 cl20 = vec3(shirr[4].z, shirr[4].w, shirr[5].x); + vec3 cl21 = vec3(shirr[5].y, shirr[5].z, shirr[5].w); + vec3 cl22 = vec3(shirr[6].x, shirr[6].y, shirr[6].z); + return ( + c1 * cl22 * (nor.y * nor.y - (-nor.z) * (-nor.z)) + + c3 * cl20 * nor.x * nor.x + + c4 * cl00 - + c5 * cl20 + + 2.0 * c1 * cl2m2 * nor.y * (-nor.z) + + 2.0 * c1 * cl21 * nor.y * nor.x + + 2.0 * c1 * cl2m1 * (-nor.z) * nor.x + + 2.0 * c2 * cl11 * nor.y + + 2.0 * c2 * cl1m1 * (-nor.z) + + 2.0 * c2 * cl10 * nor.x + ); +} +"; + + public static var str_envMapEquirect = " +vec2 envMapEquirect(const vec3 normal) { + const float PI = 3.1415926535; + const float PI2 = PI * 2.0; + float phi = acos(normal.z); + float theta = atan(-normal.y, normal.x) + PI; + return vec2(theta / PI2, phi / PI); +} +"; + + // Linearly Transformed Cosines + // https://eheitzresearch.wordpress.com/415-2/ + public static var str_ltcEvaluate = " +float integrateEdge(vec3 v1, vec3 v2) { + float cosTheta = dot(v1, v2); + float theta = acos(cosTheta); + float res = cross(v1, v2).z * ((theta > 0.001) ? theta / sin(theta) : 1.0); + return res; +} +int clipQuadToHorizon(inout vec3 L[5]) { + int n = 0; + int config = 0; + if (L[0].z > 0.0) config += 1; + if (L[1].z > 0.0) config += 2; + if (L[2].z > 0.0) config += 4; + if (L[3].z > 0.0) config += 8; + if (config == 0) {} + else if (config == 1) { // V1 clip V2 V3 V4 + n = 3; + L[1] = -L[1].z * L[0] + L[0].z * L[1]; + L[2] = -L[3].z * L[0] + L[0].z * L[3]; + } + else if (config == 2) { // V2 clip V1 V3 V4 + n = 3; + L[0] = -L[0].z * L[1] + L[1].z * L[0]; + L[2] = -L[2].z * L[1] + L[1].z * L[2]; + } + else if (config == 3) { // V1 V2 clip V3 V4 + n = 4; + L[2] = -L[2].z * L[1] + L[1].z * L[2]; + L[3] = -L[3].z * L[0] + L[0].z * L[3]; + } + else if (config == 4) { // V3 clip V1 V2 V4 + n = 3; + L[0] = -L[3].z * L[2] + L[2].z * L[3]; + L[1] = -L[1].z * L[2] + L[2].z * L[1]; + } + else if (config == 5) { // V1 V3 clip V2 V4) impossible + n = 0; + } + else if (config == 6) { // V2 V3 clip V1 V4 + n = 4; + L[0] = -L[0].z * L[1] + L[1].z * L[0]; + L[3] = -L[3].z * L[2] + L[2].z * L[3]; + } + else if (config == 7) { // V1 V2 V3 clip V4 + n = 5; + L[4] = -L[3].z * L[0] + L[0].z * L[3]; + L[3] = -L[3].z * L[2] + L[2].z * L[3]; + } + else if (config == 8) { // V4 clip V1 V2 V3 + n = 3; + L[0] = -L[0].z * L[3] + L[3].z * L[0]; + L[1] = -L[2].z * L[3] + L[3].z * L[2]; + L[2] = L[3]; + } + else if (config == 9) { // V1 V4 clip V2 V3 + n = 4; + L[1] = -L[1].z * L[0] + L[0].z * L[1]; + L[2] = -L[2].z * L[3] + L[3].z * L[2]; + } + else if (config == 10) { // V2 V4 clip V1 V3) impossible + n = 0; + } + else if (config == 11) { // V1 V2 V4 clip V3 + n = 5; + L[4] = L[3]; + L[3] = -L[2].z * L[3] + L[3].z * L[2]; + L[2] = -L[2].z * L[1] + L[1].z * L[2]; + } + else if (config == 12) { // V3 V4 clip V1 V2 + n = 4; + L[1] = -L[1].z * L[2] + L[2].z * L[1]; + L[0] = -L[0].z * L[3] + L[3].z * L[0]; + } + else if (config == 13) { // V1 V3 V4 clip V2 + n = 5; + L[4] = L[3]; + L[3] = L[2]; + L[2] = -L[1].z * L[2] + L[2].z * L[1]; + L[1] = -L[1].z * L[0] + L[0].z * L[1]; + } + else if (config == 14) { // V2 V3 V4 clip V1 + n = 5; + L[4] = -L[0].z * L[3] + L[3].z * L[0]; + L[0] = -L[0].z * L[1] + L[1].z * L[0]; + } + else if (config == 15) { // V1 V2 V3 V4 + n = 4; + } + if (n == 3) L[3] = L[0]; + if (n == 4) L[4] = L[0]; + return n; +} +float ltcEvaluate(vec3 N, vec3 V, float dotNV, vec3 P, mat3 Minv, vec3 points0, vec3 points1, vec3 points2, vec3 points3) { + vec3 T1, T2; + T1 = normalize(V - N * dotNV); + T2 = cross(N, T1); + Minv = mul(transpose(mat3(T1, T2, N)), Minv); + vec3 L[5]; + L[0] = mul(points0 - P, Minv); + L[1] = mul(points1 - P, Minv); + L[2] = mul(points2 - P, Minv); + L[3] = mul(points3 - P, Minv); + L[4] = vec3(0.0, 0.0, 0.0); + int n = clipQuadToHorizon(L); + if (n == 0) return 0.0; + L[0] = normalize(L[0]); + L[1] = normalize(L[1]); + L[2] = normalize(L[2]); + L[3] = normalize(L[3]); + L[4] = normalize(L[4]); + float sum = 0.0; + sum += integrateEdge(L[0], L[1]); + sum += integrateEdge(L[1], L[2]); + sum += integrateEdge(L[2], L[3]); + if (n >= 4) sum += integrateEdge(L[3], L[4]); + if (n == 5) sum += integrateEdge(L[4], L[0]); + return max(0.0, -sum); +} "; } diff --git a/Sources/arm/node/MaterialShader.hx b/Sources/arm/node/MaterialShader.hx index 4b236716..05babd29 100644 --- a/Sources/arm/node/MaterialShader.hx +++ b/Sources/arm/node/MaterialShader.hx @@ -319,7 +319,7 @@ class MaterialShader { s += '#define fract frac\n'; s += '#define mix lerp\n'; // s += '#define fma mad\n'; - // s += '#define atan(x, y) atan2(y, x)\n'; + s += '#define atan(x, y) atan2(y, x)\n'; // s += '#define clamp(x, 0.0, 1.0) saturate(x)\n'; s += header; diff --git a/Sources/arm/ui/BoxPreferences.hx b/Sources/arm/ui/BoxPreferences.hx index 0193b983..7a2de7ff 100644 --- a/Sources/arm/ui/BoxPreferences.hx +++ b/Sources/arm/ui/BoxPreferences.hx @@ -5,6 +5,7 @@ import haxe.Json; import zui.Id; import zui.Zui; import iron.data.Data; +import iron.RenderPath; import arm.node.MaterialParser; import arm.data.LayerSlot; import arm.io.ImportPlugin; @@ -12,6 +13,9 @@ import arm.io.ImportKeymap; import arm.io.ImportTheme; import arm.sys.Path; import arm.sys.File; +import arm.render.RenderPathDeferred; +import arm.render.RenderPathForward; +import arm.Enums; class BoxPreferences { @@ -281,6 +285,21 @@ class BoxPreferences { Context.hsupersample = Id.handle({position: Config.getSuperSampleQuality(Config.raw.rp_supersample)}); Context.hvxao = Id.handle({selected: Config.raw.rp_gi}); if (ui.tab(htab, tr("Viewport"), true)) { + var hrendermode = Id.handle({position: Context.renderMode}); + Context.renderMode = ui.combo(hrendermode, ["Full", "Mobile"], tr("Renderer"), true); + if (hrendermode.changed) { + if (hrendermode.position == RenderForward) { + if (RenderPathForward.path == null) { + RenderPathForward.init(RenderPath.active); + } + RenderPath.active.commands = RenderPathForward.commands; + } + else { + RenderPath.active.commands = RenderPathDeferred.commands; + } + MaterialParser.parseMeshMaterial(); + } + ui.combo(Context.hsupersample, ["0.25x", "0.5x", "1.0x", "1.5x", "2.0x", "4.0x"], tr("Super Sample"), true); if (Context.hsupersample.changed) Config.applyConfig(); @@ -290,31 +309,33 @@ class BoxPreferences { if (vsyncHandle.changed) Config.save(); #end - #if rp_voxelao - ui.check(Context.hvxao, tr("Voxel AO")); - if (ui.isHovered) ui.tooltip(tr("Cone-traced AO and shadows")); - if (Context.hvxao.changed) { - Config.applyConfig(); - #if arm_creator - MaterialParser.parseMeshMaterial(); - #end - } + if (Context.renderMode == RenderDeferred) { + #if rp_voxelao + ui.check(Context.hvxao, tr("Voxel AO")); + if (ui.isHovered) ui.tooltip(tr("Cone-traced AO and shadows")); + if (Context.hvxao.changed) { + Config.applyConfig(); + #if arm_creator + MaterialParser.parseMeshMaterial(); + #end + } - ui.enabled = Context.hvxao.selected; - var h = Id.handle({value: Context.vxaoOffset}); - Context.vxaoOffset = ui.slider(h, tr("Cone Offset"), 1.0, 4.0, true); - if (h.changed) Context.ddirty = 2; - var h = Id.handle({value: Context.vxaoAperture}); - Context.vxaoAperture = ui.slider(h, tr("Aperture"), 1.0, 4.0, true); - if (h.changed) Context.ddirty = 2; - ui.enabled = true; - #end - ui.check(Context.hssgi, tr("SSAO")); - if (Context.hssgi.changed) Config.applyConfig(); - ui.check(Context.hssr, tr("SSR")); - if (Context.hssr.changed) Config.applyConfig(); - ui.check(Context.hbloom, tr("Bloom")); - if (Context.hbloom.changed) Config.applyConfig(); + ui.enabled = Context.hvxao.selected; + var h = Id.handle({value: Context.vxaoOffset}); + Context.vxaoOffset = ui.slider(h, tr("Cone Offset"), 1.0, 4.0, true); + if (h.changed) Context.ddirty = 2; + var h = Id.handle({value: Context.vxaoAperture}); + Context.vxaoAperture = ui.slider(h, tr("Aperture"), 1.0, 4.0, true); + if (h.changed) Context.ddirty = 2; + ui.enabled = true; + #end + ui.check(Context.hssgi, tr("SSAO")); + if (Context.hssgi.changed) Config.applyConfig(); + ui.check(Context.hssr, tr("SSR")); + if (Context.hssr.changed) Config.applyConfig(); + ui.check(Context.hbloom, tr("Bloom")); + if (Context.hbloom.changed) Config.applyConfig(); + } var h = Id.handle({value: Config.raw.rp_vignette}); Config.raw.rp_vignette = ui.slider(h, tr("Vignette"), 0.0, 1.0, true); diff --git a/Sources/arm/ui/UIMenu.hx b/Sources/arm/ui/UIMenu.hx index dd5a11f6..196c9570 100644 --- a/Sources/arm/ui/UIMenu.hx +++ b/Sources/arm/ui/UIMenu.hx @@ -239,7 +239,7 @@ class UIMenu { tr("Object Normal"), tr("Material ID"), tr("Object ID"), - tr("Mask"), + tr("Mask") ]; #if kha_direct3d12 modes.push(tr("Path Traced")); @@ -250,12 +250,16 @@ class UIMenu { Context.viewportMode = modeHandle.position; if (modeHandle.changed) { - var deferred = Context.viewportMode == ViewRender || Context.viewportMode == ViewPathTrace; + var deferred = Context.renderMode != RenderForward && (Context.viewportMode == ViewLit || Context.viewportMode == ViewPathTrace); if (deferred) { RenderPath.active.commands = RenderPathDeferred.commands; } + // else if (Context.viewportMode == ViewPathTrace) { + // } else { - if (RenderPathForward.path == null) RenderPathForward.init(RenderPath.active); + if (RenderPathForward.path == null) { + RenderPathForward.init(RenderPath.active); + } RenderPath.active.commands = RenderPathForward.commands; } MaterialParser.parseMeshMaterial();