diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d9f8c5d756489d1bf27156e11a1776e190504a62..7a65b78b2e51d6715047f525f9c395a1bdd8a30f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,7 +18,7 @@ variables: # precompiled / assets # the URLs are the same as inside the game, change both if updating WEBGL_NAME: "WebGL art assets" - WEBGL_URL: "https://mega.nz/folder/m1JQ3JAQ#2GJsM7csBBvBu0DX8SB2kA" + WEBGL_URL: "https://mega.nz/folder/DtoyiSwC#5FLXDYr1Uy90PZjxmfrKXA" RENDER_NAME: "Rendered imagepack (outdated)" RENDER_URL: "https://mega.nz/file/upoAlBaZ#EbZ5wCixxZxBhMN_ireJTXt0SIPOywO2JW9XzTIPhe0" diff --git a/js/003-data/gameVariableData.js b/js/003-data/gameVariableData.js index 2977f3da51072e7e1b4e87e3882b2b56c94703d3..0d7f4a6b8bd84be738d56e4899da8bb196fe5983 100644 --- a/js/003-data/gameVariableData.js +++ b/js/003-data/gameVariableData.js @@ -155,6 +155,7 @@ App.Data.defaultGameStateVariables = { setTextureResolution: 1024, setShadowMapping: true, setSSAO: true, + setSSS: true, setTonemapping: true, setImageSize: 1, showAgeDetail: 1, diff --git a/src/art/artJS.js b/src/art/artJS.js index 64ebc0a0bb7c10546e668ca8a7448e51fb84a533..7c360aa63c52df6229968f1d7e2c1db25a9beaf9 100644 --- a/src/art/artJS.js +++ b/src/art/artJS.js @@ -198,6 +198,7 @@ App.Art.webglInitialize = function() { try { // load model/morphs/textures assets let sceneData = App.Art.sceneGetData(); + App.Art.sceneGetData = function(){}; // load default dictionary containing camera/light/morph/material values let scene = App.Art.sceneGetParams(); scene.lockView = false; @@ -226,12 +227,27 @@ App.Art.webglInitialize = function() { }; script.src = "resources/webgl/scene1/scene1.js"; + let settings = document.createElement("script"); + settings.onload = function() { + try { + document.head.appendChild(script); + } catch (e) { + App.Art.errorHandler("engineFailed", 1); + LoadScreen.unlock(loadLockID); + } + }; + settings.onerror = function() { + App.Art.errorHandler("engineFailed", 2); + LoadScreen.unlock(loadLockID); + }; + settings.src = "resources/webgl/scene1/settings.json"; + // asynchronously load webgl assets if present let load = document.createElement("script"); load.onload = function() { // but only if version is correct - if (App.Art.version === "1.10") { - document.head.appendChild(script); + if (App.Art.version === "1.11") { + document.head.appendChild(settings); } else { App.Art.errorHandler("engineFailed", 3); LoadScreen.unlock(loadLockID); diff --git a/src/art/webgl/art.js b/src/art/webgl/art.js index cc0efd6538d9f45b7f829e592bd3d581e7a2b7a2..ba23fdb80a5d670bffcc7299cd16c0d640b58a89 100644 --- a/src/art/webgl/art.js +++ b/src/art/webgl/art.js @@ -241,7 +241,7 @@ App.Art.applyFigures = function(slave, scene, p) { break; case "a slutty maid outfit": figures.push("Slutty Maid Dress", "Slutty Maid Bands", "Slutty Maid Neck Bow", "Slutty Maid Shoes", "Slutty Maid Headband"); - p.hideDick = true; + p.hideDick = false; p.applyNipples = false; p.hideHair = false; p.applyPumps = true; @@ -262,7 +262,7 @@ App.Art.applyFigures = function(slave, scene, p) { break; case "a biyelgee costume": figures.push("Biyelgee Dress", "Biyelgee Hat"); - p.hideDick = true; + p.hideDick = false; p.applyNipples = false; p.hideHair = false; break; @@ -370,7 +370,7 @@ App.Art.applyFigures = function(slave, scene, p) { break; case "an oversized t-shirt": figures.push("Oversized Shirt"); - p.hideDick = false; + p.hideDick = true; p.applyNipples = false; p.hideHair = false; break; @@ -546,7 +546,7 @@ App.Art.applyFigures = function(slave, scene, p) { break; case "slutty jewelry": figures.push("Bangles"); - p.hideDick = true; + p.hideDick = false; p.applyNipples = true; p.hideHair = false; break; @@ -586,8 +586,8 @@ App.Art.applyFigures = function(slave, scene, p) { p.hideHair = false; p.applyPanty = true; break; - case "kitty lingerie": // placeholder since bunny is duplicate - figures.push("Bunny Outfit"); + case "kitty lingerie": + figures.push("Kitty Choker", "Kitty Top", "Kitty Panty"); p.applyPanty = false; p.hideDick = true; p.applyNipples = false; @@ -599,6 +599,34 @@ App.Art.applyFigures = function(slave, scene, p) { p.applyNipples = true; p.hideHair = false; break; + case "a schoolgirl outfit": + figures.push("School Girl Belly Piercing", "School Girl Choker", "School Girl Panty", "School Girl Skirt", "School Girl Stockings", "School Girl Shirt"); + p.applyPanty = false; + p.hideDick = true; + p.applyNipples = false; + p.hideHair = false; + break; + case "slutty business attire": + figures.push("Secretary Glasses", "Secretary Skirt", "Secretary Vest", "Secretary Stockings"); + p.applyPanty = true; + p.hideDick = true; + p.applyNipples = false; + p.hideHair = false; + break; + case "a monokini": + figures.push("Monokini"); + p.applyPanty = false; + p.hideDick = true; + p.applyNipples = false; + p.hideHair = false; + break; + case "a string bikini": + figures.push("String Bikini Bottom", "String Bikini Top"); + p.applyPanty = false; + p.hideDick = true; + p.applyNipples = false; + p.hideHair = false; + break; } if (p.underage) { @@ -608,17 +636,48 @@ App.Art.applyFigures = function(slave, scene, p) { p.hideDick = true; } - if (slave.chastityAnus && slave.chastityVagina) { - figures.push("Chastity Belt Base", "Chastity Belt Vaginal Cap with Holes", "Chastity Belt Anal Cap with Hole"); - p.hideDick = true; + /* + switch (slave.vaginalAccessory) { + case "dildo": + figures.push("Dildo 1"); + break; } - if (!slave.chastityAnus && slave.chastityVagina) { - figures.push("Chastity Belt Base", "Chastity Belt Vaginal Cap with Holes"); - p.hideDick = true; + + switch (slave.buttplug) { + case "plug": + figures.push("Anal Plug 1"); + break; + case "large plug": + figures.push("Anal Plug 2"); + break; + case "huge plug": + figures.push("Anal Plug 3"); + break; + case "long plug": + figures.push("Anal Plug 1"); + break; + case "long, large plug": + figures.push("Anal Plug 2"); + break; + case "long, huge plug": + figures.push("Anal Plug 3"); + break; } - if (slave.chastityAnus && !slave.chastityVagina) { - figures.push("Chastity Belt Base", "Chastity Belt Anal Cap with Hole"); - p.hideDick = true; + */ + + if (slave.chastityAnus || slave.chastityVagina || slave.chastityPenis) { + figures.push("Chastity Belt Base"); + if (slave.chastityAnus) { + figures.push("Chastity Belt Anal Cap with Hole"); + } + if (slave.chastityVagina) { + figures.push("Chastity Belt Vaginal Cap with Holes"); + p.hideDick = true; + } + if (slave.chastityPenis) { + figures.push("Chastity Belt Vaginal Cap with Holes"); + p.hideDick = true; + } } switch (slave.bellyAccessory) { @@ -656,10 +715,10 @@ App.Art.applyFigures = function(slave, scene, p) { break; } - if (!hasLeftArm(slave) && slave.PLimb > 0) { + if (!slave.arm.left && slave.PLimb > 0) { figures.push("Amputee Cap Arm Left"); } - if (!hasRightArm(slave) && slave.PLimb > 0) { + if (!slave.arm.right && slave.PLimb > 0) { figures.push("Amputee Cap Arm Right"); } @@ -1494,15 +1553,15 @@ App.Art.applyMaterials = function(slave, scene, p) { materials.push(["Eyelashes", "map_D", "base2/eyelash/EyeLash_4.jpg"]); } - let irisColorLeft = App.Art.hexToRgb(extractColor(hasLeftEye(slave) ? extractColor(slave.eye.left.iris) : extractColor("black"))); - let irisColorRight = App.Art.hexToRgb(extractColor(hasRightEye(slave) ? extractColor(slave.eye.right.iris) : extractColor("black"))); - let scleraColorLeft = App.Art.hexToRgb(extractColor(hasLeftEye(slave) ? extractColor(slave.eye.left.sclera) : extractColor("black"))); - let scleraColorRight = App.Art.hexToRgb(extractColor(hasRightEye(slave) ? extractColor(slave.eye.right.sclera) : extractColor("black"))); + let irisColorLeft = App.Art.hexToRgb(extractColor(slave.eye.left ? extractColor(slave.eye.left.iris) : extractColor("black"))); + let irisColorRight = App.Art.hexToRgb(extractColor(slave.eye.right ? extractColor(slave.eye.right.iris) : extractColor("black"))); + let scleraColorLeft = App.Art.hexToRgb(extractColor(slave.eye.left ? extractColor(slave.eye.left.sclera) : extractColor("black"))); + let scleraColorRight = App.Art.hexToRgb(extractColor(slave.eye.right ? extractColor(slave.eye.right.sclera) : extractColor("black"))); - materials.push(["Iris_Left", "Ka", [irisColorLeft[0]*1.4, irisColorLeft[1]*1.4, irisColorLeft[2]*1.4]]); - materials.push(["Iris_Right", "Ka", [irisColorRight[0]*1.4, irisColorRight[1]*1.4, irisColorRight[2]*1.4]]); - materials.push(["Sclera_Left", "Ka", [scleraColorLeft[0]*0.75, scleraColorLeft[1]*0.75, scleraColorLeft[2]*0.75]]); - materials.push(["Sclera_Right", "Ka", [scleraColorRight[0]*0.75, scleraColorRight[1]*0.75, scleraColorRight[2]*0.75]]); + materials.push(["Iris_Left", "Ka", [irisColorLeft[0]*1.4*2.5, irisColorLeft[1]*1.4*2.5, irisColorLeft[2]*1.4*2.5]]); + materials.push(["Iris_Right", "Ka", [irisColorRight[0]*1.4*2.5, irisColorRight[1]*1.4*2.5, irisColorRight[2]*1.4*2.5]]); + materials.push(["Sclera_Left", "Ka", [scleraColorLeft[0]*0.8*1.8, scleraColorLeft[1]*0.8*2, scleraColorLeft[2]*0.8*2]]); + materials.push(["Sclera_Right", "Ka", [scleraColorRight[0]*0.8*1.8, scleraColorRight[1]*0.8*2, scleraColorRight[2]*0.8*2]]); // expected skin color let O = App.Art.hexToRgb(skinColorCatcher(slave).skinColor); @@ -1534,20 +1593,23 @@ App.Art.applyMaterials = function(slave, scene, p) { let Ka; let lKa; let r; + let rN; let Ni; let skin; let cockSkin; if (slave.clothes === "body oil") { r = 0.4; + rN = 0.4; Ni = 1.5; } else { r = 0.7; + rN = 0.55; Ni = 1.35; } if (sqAO < sqBO && sqAO < sqCO && sqAO < sqDO) { - Ks = [2, 2, 2]; + Ks = [2.5, 2.5, 2.5]; Ka = mA; lKa = lA; skin = "WhiteTone"; @@ -1560,7 +1622,7 @@ App.Art.applyMaterials = function(slave, scene, p) { materials.push(["nipple_mask", "map_Ka", "base/skin/torso white.jpg"]); materials.push(["nipple_mask", "Ks", [3, 3, 3]]); } else if (sqBO < sqAO && sqBO < sqCO && sqBO < sqDO) { - Ks = [1.75, 1.75, 1.75]; + Ks = [2.5, 2.5, 2.5]; Ka = mB; lKa = lB; skin = "LightTone"; @@ -1573,7 +1635,7 @@ App.Art.applyMaterials = function(slave, scene, p) { materials.push(["nipple_mask", "map_Ka", "base/skin/torso light.jpg"]); materials.push(["nipple_mask", "Ks", [2.5, 2.5, 2.5]]); } else if (sqCO < sqBO && sqCO < sqAO && sqCO < sqDO) { - Ks = [1.5, 1.5, 1.5]; + Ks = [2, 2, 2]; Ka = mC; lKa = lC; skin = "MidTone"; @@ -1586,7 +1648,7 @@ App.Art.applyMaterials = function(slave, scene, p) { materials.push(["nipple_mask", "map_Ka", "base/skin/torso mid.jpg"]); materials.push(["nipple_mask", "Ks", [2, 2, 2]]); } else if (sqDO < sqBO && sqDO < sqCO && sqDO < sqAO) { - Ks = [0.75, 0.75, 0.75]; + Ks = [1, 1, 1]; Ka = mD; lKa = lD; skin = "DarkTone"; @@ -1634,7 +1696,7 @@ App.Art.applyMaterials = function(slave, scene, p) { materials.push([skin + "Genitalia", "Ks", Ks]); materials.push([skin + "Genitalia", "r", r]); materials.push([skin + "Genitalia", "Ni", Ni]); - materials.push(["nipple_mask", "r", r]); + materials.push(["nipple_mask", "r", rN]); materials.push(["nipple_mask", "Ni", Ni]); let pubicColor = App.Art.hexToRgb(extractColor(slave.pubicHColor)); @@ -1695,7 +1757,7 @@ App.Art.applyMaterials = function(slave, scene, p) { materials.push(["skindetail_fine_arms", "d", fineDetail]); materials.push(["skindetail_fine_legs", "d", fineDetail]); - let veins = 1; + let veins = 0; materials.push(["skindetail_veins_torso", "d", veins]); materials.push(["skindetail_veins_face", "d", veins]); materials.push(["skindetail_veins_arms", "d", veins]); @@ -1784,10 +1846,10 @@ App.Art.applyMorphs = function(slave, scene, p) { morphs.push(["posesExtremeHeels2", 1]); } - if (hasBothArms(slave) && hasBothLegs(slave)) { + if (slave.arm.right && slave.arm.left && slave.leg.right && slave.leg.left) { if (scene.inspect) { - morphs.push(["posesInspect", 1]); - morphs.push(["posesInspectGen", 1]); + morphs.push(["posesInspect2", 1]); + morphs.push(["posesInspectGen2", 1]); } else if (slave.devotion > 50) { morphs.push(["posesHigh", 1]); } else if (slave.devotion > -20) { @@ -1795,6 +1857,11 @@ App.Art.applyMorphs = function(slave, scene, p) { } else { morphs.push(["posesLow", 1]); } + + if (slave.devotion <= 50) { + morphs.push(["posesArmsDown", -slave.weight/300/3.5]); + morphs.push(["posesLegsClosed", -slave.weight/300/3.5]); + } } if (slave.trust < 0) { @@ -2136,7 +2203,7 @@ App.Art.applyMorphs = function(slave, scene, p) { let foreheadShape = ["foreheadShapeNormal", "foreheadShapeRound", "foreheadShapeSmall"]; let forehead = Math.floor(App.Art.random() * foreheadShape.length); if (forehead > 0) { - morphs.push(foreheadShape[forehead], 1); + morphs.push([foreheadShape[forehead], 1]); } switch (slave.faceShape) { @@ -2154,6 +2221,144 @@ App.Art.applyMorphs = function(slave, scene, p) { morphs.push(["faceShapeExotic", 1]); break; } + + /* + const morphs_eyes = [ + ["", 1], + ["mEyeShape1",1], + ["mEyeShape2",1], + ["mEyeShape3",0.5], + ["mEyeShape4",1], + ["mEyeShape5",1], + ["mEyeShape6",1], + ["mEyeShape7",1], + ["mEyeShape8",1], + ]; + const morphs_nose = [ + ["", 1], + ["mNoseShape1",0.5], + ["mNoseShape2",1], + ["mNoseShape3",0.5], + ["mNoseShape4",0.5], + ["mNoseShape5",0.5], + ["mNoseShape6",1], + ["mNoseShape7",1], + ["mNoseShape8",1], + ["mNoseShape9",1], + ["mNoseShape10",1], + ]; + const morphs_jaw = [ + ["", 1], + ["mJawShape1", 0.5], + ["mJawShape2", 0.5], + ["mJawShape3", 0.75], + ["mJawShape4", 0.5], + ["mJawShape5", 1], + ["mJawShape6", 0.5], + ]; + const morphs_lips = [ + ["", 1], + ["mLipsShape1",1], + ["mLipsShape2",1], + ["mLipsShape3",1], + ["mLipsShape4",1], + ["mLipsShape5",1], + ["mLipsShape6",1], + ["mLipsShape7",1], + ["mLipsShape8", 0.5], + ["mLipsShape9",1], + ["mLipsShape10", 0.75], + ["mLipsShape11",1], + ["mLipsShape12", 0.5], + ]; + const morphs_cheeks = [ + ["", 1], + ["mCheeksShape1",0.5], + ["mCheeksShape2",0.5], + ["mCheeksShape3",0.5], + ["mCheeksShape4",1], + ["mCheeksShape5",1], + ["mCheeksShape6",0.5], + ["mCheeksShape7",1], + ["mCheeksShape8",1], + ]; + + const morphs_cheeks2 = [ + ["", 1], + ["mCheeks2Shape1",0.5], + ["mCheeks2Shape2",0.5], + ["mCheeks2Shape3",1], + ["mCheeks2Shape4",0.5], + ]; + + const morphs_chin = [ + ["", 1], + ["mChinShape1", 0.5], + ["mChinShape2", 0.5], + ["mChinShape3", 0.5], + ["mChinShape4", 0.5], + ]; + + const morphs_unique = [ + ["", 1], + ["mUniqueShape1", 1], + ["mUniqueShape2", 0.75], + ["mUniqueShape3", 0.5], + ["mUniqueShape4", 0.5], + ["mUniqueShape5", 0.5], + ]; + + const eyeSize = App.Art.random(); + const morphs_other = [ + ["mChinTweak1", 0.3*App.Art.random()], + ["mEyeTweak1", 0.4*eyeSize], + ["mEyeTweak2", 0.4*eyeSize], + ["mEyeTweak3", 0.25*eyeSize], + ["mEyeTweak4", 0.3*(App.Art.random()*2-1)], + ["mEyeTweak5", 0.5*(App.Art.random()*2-1)], + ]; + + const morphs_forehead = [ + ["", 1], + ["foreheadShapeRound", 1], + ["foreheadShapeSmall", 1], + ]; + + morphs.push(morphs_other[0]); + morphs.push(morphs_other[1]); + morphs.push(morphs_other[2]); + morphs.push(morphs_other[3]); + morphs.push(morphs_other[4]); + morphs.push(morphs_other[5]); + + let forehead = Math.floor(App.Art.random() * morphs_forehead.length); + if (forehead > 0) { morphs.push(morphs_forehead[forehead]); } + + let unqiue = Math.floor(App.Art.random() * morphs_unique.length); + if (unqiue > 0) { morphs.push(morphs_unique[unqiue]); } + + let chin = Math.floor(App.Art.random() * morphs_chin.length); + if (chin > 0) { morphs.push(morphs_chin[chin]); } + + let cheeks2 = Math.floor(App.Art.random() * morphs_cheeks2.length); + if (cheeks2 > 0) { morphs.push(morphs_cheeks2[cheeks2]); } + + let cheeks = Math.floor(App.Art.random() * morphs_cheeks.length); + if (cheeks > 0) { morphs.push(morphs_cheeks[cheeks]); } + + let lips = Math.floor(App.Art.random() * morphs_lips.length); + if (lips > 0) { morphs.push(morphs_lips[lips]); } + + let jaw = Math.floor(App.Art.random() * morphs_jaw.length); + if (jaw > 0) { morphs.push(morphs_jaw[jaw]); } + + let nose = Math.floor(App.Art.random() * morphs_nose.length); + if (nose > 0) { morphs.push(morphs_nose[nose]); } + + let eyes = Math.floor(App.Art.random() * morphs_eyes.length); + if (eyes > 0) { morphs.push(morphs_eyes[eyes]); } + */ + if (slave.boobs < 600) { morphs.push(["boobShapeSmall", -(slave.boobs-600)/600]); } else { @@ -2169,7 +2374,7 @@ App.Art.applyMorphs = function(slave, scene, p) { case "downward-facing": morphs.push(["boobShapeDownward", ((slave.boobs-600)**(1/3)/16) * (175/p.height)]); break; case "wide-set": - morphs.push(["boobShapeWide", ((slave.boobs-600)**(1/3)/9) * (175/p.height)]); break; + morphs.push(["boobShapeWide", ((slave.boobs-600)**(1/3)/4) * (175/p.height)]); break; case "spherical": // special case to make nipple work if (slave.nipples === "flat" || slave.nipples === "inverted" || !p.applyNipples) { @@ -2201,9 +2406,12 @@ App.Art.applyMorphs = function(slave, scene, p) { } } - if (slave.foreskin !== 0 && !scene.inspect) { - morphs.push(["foreskin", 1]); + let shaftShape = ["shaftShape0", "shaftShape1", "shaftShape2", "shaftShape3", "shaftShape4", "shaftShape5", "shaftShape6"]; + let shaft = Math.floor(App.Art.random() * shaftShape.length); + if (shaft > 0) { + morphs.push([shaftShape[shaft], 1]); } + if (slave.dick === 0 && !(slave.scrotum <= 0 || slave.balls <= 0)) { morphs.push(["dickRemove", 1]); } else if (slave.dick !== 0) { @@ -2220,7 +2428,7 @@ App.Art.applyMorphs = function(slave, scene, p) { morphs.push(["ballsRemove", 1]); } else { if (slave.balls > 1) { - morphs.push(["balls", convertRange(2, 10, 0, 0.75, slave.balls * (175/p.height))]); + morphs.push(["balls", convertRange(2, 10, 0, 0.75, slave.balls * 0.6 *(175/p.height))]); } if (slave.scrotum > 0) { @@ -2237,11 +2445,7 @@ App.Art.applyMorphs = function(slave, scene, p) { morphs.push(["muscles", slave.muscles/33]); } - if (scene.inspect) { - morphs.push(["belly", Math.max(0.7, slave.belly**(1/3)/24.6)]); // fix - } else { - morphs.push(["belly", slave.belly**(1/3)/24.6]); - } + morphs.push(["belly2", slave.belly**(1/3)/24.6]); morphs.push(["hips", slave.hips/2]); @@ -2258,7 +2462,7 @@ App.Art.applyMorphs = function(slave, scene, p) { } if (slave.weight >= 0) { - morphs.push(["weight", slave.weight/75]); + morphs.push(["weight2", slave.weight/300]); } else { morphs.push(["weightThin", -slave.weight/80]); } @@ -2269,20 +2473,24 @@ App.Art.applyMorphs = function(slave, scene, p) { morphs.push(["physicalAgeOld", (slave.visualAge-20)/52]); } - if (!hasLeftArm(slave)) { + if (!slave.arm.left) { morphs.push(["amputeeLeftArm", 1]); } - if (!hasRightArm(slave)) { + if (!slave.arm.right) { morphs.push(["amputeeRightArm", 1]); } - if (!hasLeftLeg(slave)) { + if (!slave.leg.left) { morphs.push(["amputeeLeftLeg", 1]); } - if (!hasRightLeg(slave)) { + if (!slave.leg.right) { morphs.push(["amputeeRightLeg", 1]); } - morphs.push(["offset", 3]); // only applies to clothes + if (slave.dick > 0 || slave.scrotum > 0 || slave.balls > 0) { + morphs.push(["bulge", Math.max(slave.dick/4, 0)]); + } + + morphs.push(["offset", 2]); // only applies to clothes App.Art.resetMorphs(scene); diff --git a/src/art/webgl/engine.js b/src/art/webgl/engine.js index 5786cc818315cbaf5266b6255cd4612b3d1f83ec..0e93a67728bd7858a36929fb4aba35154c3b830b 100644 --- a/src/art/webgl/engine.js +++ b/src/art/webgl/engine.js @@ -13,22 +13,18 @@ App.Art.Engine = class { in vec3 vertexPosition; in vec2 textureCoordinate; - in vec3 vertexNormalMorph; - in vec3 vertexPositionMorph; - out vec2 textureCoord; out vec3 normal; out vec3 pos; out vec4 shadowMap; void main() { - vec4 posM = vec4(vertexPosition + vertexPositionMorph, 1.0); - gl_Position = matModelViewProjection * posM; - normal = normalize((matNormal * vec4(vertexNormal + vertexNormalMorph, 1.0)).xyz); + gl_Position = matModelViewProjection * vec4(vertexPosition, 1.0); + normal = normalize((matNormal * vec4(vertexNormal, 1.0)).xyz); textureCoord = textureCoordinate; - pos = (matModelView * posM).xyz; - shadowMap = matModelViewProjectionShadow * posM; + pos = (matModelView * vec4(vertexPosition, 1.0)).xyz; + shadowMap = matModelViewProjectionShadow * vec4(vertexPosition, 1.0); }`; } @@ -62,13 +58,12 @@ App.Art.Engine = class { uniform mat4 matModel; in vec3 vertexPosition; - in vec3 vertexPositionMorph; in vec2 textureCoordinate; out vec2 textureCoord; void main() { - gl_Position = matModelViewProjectionShadow * vec4(vertexPosition + vertexPositionMorph, 1.0); + gl_Position = matModelViewProjectionShadow * vec4(vertexPosition, 1.0); textureCoord = textureCoordinate; }`; } @@ -188,6 +183,89 @@ App.Art.Engine = class { }`; } + getFsSourceSSS() { + return `#version 300 es + precision highp float; + + out float iao; + + in vec2 textureCoord; + + uniform sampler2D gPosition; + uniform sampler2D gNormal; + uniform sampler2D texNoise; + + uniform vec3 samples[32]; + + uniform mat4 projection; + uniform mat4 view; + + uniform float radius; + uniform float bias; + uniform float scale; + + void main() { + vec2 resolution = vec2(textureSize(gPosition, 0)); + vec3 pos = texture(gPosition, textureCoord).xyz; + vec3 normal = -normalize(texture(gNormal, textureCoord).rgb); + // tile noise texture over screen based on screen dimensions divided by noise size + vec3 randomVec = normalize(texture(texNoise, textureCoord * resolution / vec2(3.0, 3.0)).xyz); + + // create TBN + vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal)); + vec3 bitangent = cross(normal, tangent); + mat3 TBN = mat3(tangent, bitangent, normal); + + // iterate over the sample kernel and calculate occlusion factor + float occlusion = 0.0; + for(int i = 0; i < 32; i++) + { + // get sample position + vec3 samplePos = TBN * samples[i]; // from tangent to view-space + samplePos = pos + samplePos * radius; + + // project sample position + vec4 offset = vec4(samplePos, 1.0); + offset = projection * offset; // from view to clip-space + offset.xy /= offset.w; // perspective divide + offset.xy = offset.xy * 0.5 + 0.5; // transform to range 0.0 - 1.0 + + float sampleDepth = texture(gPosition, offset.xy).z; + + // range check & accumulate + float rangeCheck = smoothstep(0.0, 1.0, radius / abs(pos.z - sampleDepth)); + occlusion += (sampleDepth <= samplePos.z + bias ? 1.0 : 0.0) * rangeCheck; + } + iao = 1.0 - (occlusion / scale); + }`; + } + + getFsSourceSSSBlur() { + return `#version 300 es + precision highp float; + + out float iao; + in vec2 textureCoord; + + uniform sampler2D sssInput; + uniform float blur; + + void main() + { + vec2 texelSize = 1.0 / vec2(textureSize(sssInput, 0)); + float result = 0.0; + for (float x = -blur; x <= blur; x++) + { + for (float y = -blur; y <= blur; y++) + { + vec2 offset = vec2(x, y) * texelSize; + result += texture(sssInput, textureCoord + offset).r; + } + } + iao = result / ((blur*2.0+1.0) * (blur*2.0+1.0)); + }`; + } + getFsSourceForwardPass(dl, pl) { return `#version 300 es precision highp float; @@ -210,7 +288,7 @@ App.Art.Engine = class { uniform float sssPower; uniform float sssDistortion; uniform float sssIntensity; - uniform vec3 sssColor; + uniform float sssAmbient; uniform float whiteM; uniform float gammaY; @@ -225,6 +303,7 @@ App.Art.Engine = class { uniform float Ni; uniform float r; uniform float m; + uniform float sss; uniform vec2 offset; uniform float angle; @@ -233,6 +312,7 @@ App.Art.Engine = class { uniform float sNormals; uniform float sSSAO; uniform float sAO; + uniform float sIAO; uniform float sAmbient; uniform float sAlbedo; uniform float sSpecular; @@ -250,7 +330,7 @@ App.Art.Engine = class { uniform vec3 cameraPos; - uniform sampler2D textSampler[9]; + uniform sampler2D textSampler[10]; in vec2 textureCoord; in vec3 normal; @@ -352,6 +432,7 @@ App.Art.Engine = class { vec3 map_Ke = vec3(0.0,0.0,0.0); float map_d = 1.0; float ao = 1.0; + float iao = 1.0; float shadow = 1.0; float roughness = 0.0; float metallic = 0.0; @@ -376,6 +457,9 @@ App.Art.Engine = class { if (sSSAO == 1.0) ao = texture(textSampler[0], vec2(gl_FragCoord.x, gl_FragCoord.y) / resolution).r; + if (sSSS == 1.0) + iao = texture(textSampler[9], vec2(gl_FragCoord.x, gl_FragCoord.y) / resolution).r; + if (sAlbedo == 1.0) map_Ka = Ka * texture(textSampler[2], coord).rgb; @@ -394,15 +478,15 @@ App.Art.Engine = class { float bias = max(shadowBiasMax * (1.0 - dot(new_normal, shadowDir)), shadowBiasMin); vec2 texelSize = 1.0 / vec2(textureSize(textSampler[8], 0)); - for(int x = -1; x <= 1; ++x) + for(int x = -2; x <= 2; ++x) { - for(int y = -1; y <= 1; ++y) + for(int y = -2; y <= 2; ++y) { float pcfDepth = texture(textSampler[8], projCoords.xy + vec2(x, y) * texelSize).r; shadow += currentDepth - bias < pcfDepth ? 1.0 : 0.0; } } - shadow /= 9.0; + shadow /= 25.0; shadow = min(shadow + (1.0-shadowIntensity), 1.0); } @@ -429,7 +513,7 @@ App.Art.Engine = class { // cook-torrance brdf float NDF = distributionGGX(N, H, roughness); float G = geometrySmith(N, V, L, roughness); - vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0); + vec3 F = fresnelSchlickRoughness(max(dot(H, V), 0.0), F0, roughness); vec3 numerator = NDF * G * F; float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.00001; @@ -441,21 +525,26 @@ App.Art.Engine = class { // add to outgoing radiance Lo float NdotL = max(dot(N, L), 0.0); - Lo += (kD * albedo / PI + specular * map_Ks) * radiance * NdotL * shadow; + if (map_d >= 0.85 || overlay == 1.0) { + Lo += (kD * albedo / PI + specular * map_Ks) * radiance * NdotL * shadow; + } + else { + Lo += (kD * albedo / PI + specular * map_Ks) * radiance * NdotL; + } // ambient lighting - vec3 La = kD * albedo * lightAmb[i] * (1.0-pow(NdotL, 0.5)); + vec3 La = kD * albedo * lightAmb[i]; if (map_d >= 0.85 || overlay == 1.0) { - // ao - Lo += La * ao; - // sss - if (sSSS == 1.0) { + if (sSSS == 1.0 && sss == 1.0) { vec3 H_d = normalize(L + N * sssDistortion); float VdotH = pow(clamp(dot(V, -H_d), 0.0, 1.0), sssPower); - Lo += sssColor * VdotH * kD * albedo * sssIntensity; + Lo += (kD * albedo / PI + specular * map_Ks) * (VdotH+sssAmbient) * iao * sssIntensity * NdotL * shadow; } + + // ao + Lo += La * ao; } else { Lo += La; } @@ -473,7 +562,7 @@ App.Art.Engine = class { // cook-torrance brdf float NDF = distributionGGX(N, H, roughness); float G = geometrySmith(N, V, L, roughness); - vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0); + vec3 F = fresnelSchlickRoughness(max(dot(H, V), 0.0), F0, roughness); vec3 numerator = NDF * G * F; float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.00001; @@ -485,21 +574,26 @@ App.Art.Engine = class { // add to outgoing radiance Lo float NdotL = max(dot(N, L), 0.0); - Lo += (kD * albedo / PI + specular * map_Ks) * radiance * NdotL * shadow; + if (map_d >= 0.85 || overlay == 1.0) { + Lo += (kD * albedo / PI + specular * map_Ks) * radiance * NdotL * shadow; + } + else { + Lo += (kD * albedo / PI + specular * map_Ks) * radiance * NdotL; + } // ambient lighting - vec3 La = kD * albedo * pointLightAmb[i] * (1.0-pow(NdotL, 0.5)); + vec3 La = kD * albedo * pointLightAmb[i]; if (map_d >= 0.85 || overlay == 1.0) { - // ao - Lo += La * ao; - // sss - if (sSSS == 1.0) { + if (sSSS == 1.0 && sss == 1.0) { vec3 H_d = normalize(L + N * sssDistortion); float VdotH = pow(clamp(dot(V, -H_d), 0.0, 1.0), sssPower); - Lo += sssColor * VdotH * kD * albedo * sssIntensity; + Lo += (kD * albedo / PI + specular * map_Ks) * (VdotH+sssAmbient) * iao * sssIntensity * NdotL * shadow; } + + // ao + Lo += La * ao; } else { Lo += La; } @@ -523,11 +617,14 @@ App.Art.Engine = class { c = pow(c, vec3(1.0/gammaY)); if (sNormals == 1.0) - c = new_normal; + c = new_normal*0.5+0.5; if (sAO == 1.0) c = vec3(ao); + if (sIAO == 1.0) + c = vec3(iao); + outputColor = vec4(c*map_d, map_d); }`; } @@ -563,42 +660,51 @@ App.Art.Engine = class { modelBuffers.verticesPositionBuffer = []; modelBuffers.verticesNormalBuffer = []; modelBuffers.verticesTextureCoordBuffer = []; - modelBuffers.vertexCount = []; - modelBuffers.verticesMorphBuffer = []; - modelBuffers.verticesNormalMorphBuffer = []; + modelBuffers.vertexPositionBuffer = []; modelBuffers.verticesIndexBuffer = []; modelBuffers.indexSizes = []; + modelBuffers.figureIndices = []; + modelBuffers.figureSeemMaps = []; for (let i=0, count=0; i < modelData.figures.length; i++) { modelBuffers.verticesPositionBuffer[i] = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesPositionBuffer[i]); this.gl.bufferData(this.gl.ARRAY_BUFFER, this.base64ToFloat(modelData.figures[i].verts), this.gl.STATIC_DRAW); - modelBuffers.vertexCount[i] = this.gl.getBufferParameter(this.gl.ARRAY_BUFFER, this.gl.BUFFER_SIZE)/4; + modelBuffers.vertexPositionBuffer[i] = this.base64ToFloat(modelData.figures[i].verts); modelBuffers.verticesNormalBuffer[i] = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalBuffer[i]); - this.gl.bufferData(this.gl.ARRAY_BUFFER, this.base64ToFloat(modelData.figures[i].vertsn), this.gl.STATIC_DRAW); + this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(modelBuffers.vertexPositionBuffer[i].length), this.gl.STATIC_DRAW); modelBuffers.verticesTextureCoordBuffer[i] = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesTextureCoordBuffer[i]); this.gl.bufferData(this.gl.ARRAY_BUFFER, this.base64ToFloat(modelData.figures[i].texts), this.gl.STATIC_DRAW); - // return dummy morph - modelBuffers.verticesMorphBuffer[i] = this.gl.createBuffer(); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesMorphBuffer[i]); - this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(0), this.gl.STATIC_DRAW); - - modelBuffers.verticesNormalMorphBuffer[i] = this.gl.createBuffer(); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalMorphBuffer[i]); - this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(0), this.gl.STATIC_DRAW); - + modelBuffers.figureIndices[i] = []; for (let j=0; j < modelData.figures[i].surfaces.length; j++, count++) { modelBuffers.verticesIndexBuffer[count] = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, modelBuffers.verticesIndexBuffer[count]); let intArray = this.base64ToInt(modelData.figures[i].surfaces[j].vertsi); + modelBuffers.figureIndices[i] = [...modelBuffers.figureIndices[i], ...intArray]; this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, intArray, this.gl.STATIC_DRAW); modelBuffers.indexSizes[count] = intArray.length; } + + let seems = this.base64ToInt(modelData.figures[i].seems); + let seemMap = new Int32Array(seems.length*2); + let indices = modelBuffers.figureIndices[i]; + for (let j=0, h=0; j <= seems.length-3; j+=3, h+=6) { + let idx = seems[j+1]; + let value = seems[j+2]; + indices[seems[j]] = value/3; + seemMap[h] = idx; + seemMap[h+1] = value; + seemMap[h+2] = idx+1; + seemMap[h+3] = value+1; + seemMap[h+4] = idx+2; + seemMap[h+5] = value+2; + } + modelBuffers.figureSeemMaps[i] = seemMap; } this.initMorphs(modelBuffers, modelData, dir); @@ -627,6 +733,12 @@ App.Art.Engine = class { this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.ssaoColorBufferBlur); this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.R16F, screenWidth, screenHeight, 0, this.gl.RED, this.gl.FLOAT, null); + this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.sssColorBuffer); + this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.R16F, screenWidth, screenHeight, 0, this.gl.RED, this.gl.FLOAT, null); + + this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.sssColorBufferBlur); + this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.R16F, screenWidth, screenHeight, 0, this.gl.RED, this.gl.FLOAT, null); + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); } @@ -709,6 +821,30 @@ App.Art.Engine = class { this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, buffers.ssaoColorBufferBlur, 0); this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); + // SSS color buffer + buffers.sssColorBuffer = this.gl.createTexture(); + this.gl.bindTexture(this.gl.TEXTURE_2D, buffers.sssColorBuffer); + this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST); + this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST); + this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.R16F, screenWidth, screenHeight, 0, this.gl.RED, this.gl.FLOAT, null); + + buffers.sssFBO = this.gl.createFramebuffer(); + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, buffers.sssFBO); + this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, buffers.sssColorBuffer, 0); + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); + + // SSS blur buffer + buffers.sssColorBufferBlur = this.gl.createTexture(); + this.gl.bindTexture(this.gl.TEXTURE_2D, buffers.sssColorBufferBlur); + this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST); + this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST); + this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.R16F, screenWidth, screenHeight, 0, this.gl.RED, this.gl.FLOAT, null); + + buffers.sssBlurFBO = this.gl.createFramebuffer(); + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, buffers.sssBlurFBO); + this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, buffers.sssColorBufferBlur, 0); + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); + const random = function(seed) { let x = Math.sin(seed+1) * 10000; return x - Math.floor(x); @@ -749,15 +885,12 @@ App.Art.Engine = class { let promisedMorphs = []; modelBuffers.vertexPositionMorphs = []; - modelBuffers.vertexNormalMorphs = []; modelBuffers.vertexIndexMorphs = []; for (let f=0; f < modelData.figures.length; f++) { modelBuffers.vertexPositionMorphs[f] = []; - modelBuffers.vertexNormalMorphs[f] = []; modelBuffers.vertexIndexMorphs[f] = []; for (let m=0; m < modelData.morphCount; m++) { modelBuffers.vertexPositionMorphs[f].push(new Float32Array(0)); - modelBuffers.vertexNormalMorphs[f].push(new Float32Array(0)); modelBuffers.vertexIndexMorphs[f].push(new Int32Array(0)); } @@ -783,16 +916,16 @@ App.Art.Engine = class { return new Promise(function(resolve, reject) { let script = document.createElement("script"); script.onload = function() { - let morph = window.sceneBlocks[path.split("/").slice(-1)[0]]; + let name = path.split("/").slice(-1)[0]; + let morph = window.sceneBlocks[name]; - for (let i=0; i < morph.length; i+=3) { - modelBuffers.vertexPositionMorphs[m][i/3] = engine.base64ToFloat(morph[i+0]); - modelBuffers.vertexNormalMorphs[m][i/3] = engine.base64ToFloat(morph[i+1]); + for (let i=0; i < morph.length; i+=2) { + modelBuffers.vertexPositionMorphs[m][i/2] = engine.base64ToFloat(morph[i+0]); // reconstruct compressed indices - modelBuffers.vertexIndexMorphs[m][i/3] = engine.base64ToInt(morph[i+2]).map((sum => value => sum += value)(0)); + modelBuffers.vertexIndexMorphs[m][i/2] = engine.base64ToInt(morph[i+1]).map((sum => value => sum += value)(0)); } - morph = null; // let garbage collector clean + window.sceneBlocks[name] = null; // let garbage collector clean resolve(); }; script.onerror = function(e) { @@ -830,7 +963,8 @@ App.Art.Engine = class { return new Promise(function(resolve, reject) { let script = document.createElement("script"); script.onload = function() { - let url = window.sceneBlocks[path.split("/").slice(-1)[0]][0]; + let name = path.split("/").slice(-1)[0]; + let url = window.sceneBlocks[name][0]; let img = document.createElement("img"); img.onload = function() { @@ -868,7 +1002,7 @@ App.Art.Engine = class { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img); - url = null; // let garbage collector clean + window.sceneBlocks[name] = null; // let garbage collector clean resolve(); }; img.src = url; @@ -911,6 +1045,14 @@ App.Art.Engine = class { this.gl.shaderSource(fragmentShaderSSAOBlur, this.getFsSourceSSAOBlur()); this.gl.compileShader(fragmentShaderSSAOBlur); + let fragmentShaderSSS = this.gl.createShader(this.gl.FRAGMENT_SHADER); + this.gl.shaderSource(fragmentShaderSSS, this.getFsSourceSSS()); + this.gl.compileShader(fragmentShaderSSS); + + let fragmentShaderSSSBlur = this.gl.createShader(this.gl.FRAGMENT_SHADER); + this.gl.shaderSource(fragmentShaderSSSBlur, this.getFsSourceSSSBlur()); + this.gl.compileShader(fragmentShaderSSSBlur); + let fragmentShaderForwardPass = this.gl.createShader(this.gl.FRAGMENT_SHADER); this.gl.shaderSource(fragmentShaderForwardPass, this.getFsSourceForwardPass(sceneParams.directionalLights.length, sceneParams.pointLights.length)); this.gl.compileShader(fragmentShaderForwardPass); @@ -935,6 +1077,16 @@ App.Art.Engine = class { this.gl.attachShader(this.shaderProgramSSAOBlur, fragmentShaderSSAOBlur); this.gl.linkProgram(this.shaderProgramSSAOBlur); + this.shaderProgramSSS = this.gl.createProgram(); + this.gl.attachShader(this.shaderProgramSSS, vertexShaderQuad); + this.gl.attachShader(this.shaderProgramSSS, fragmentShaderSSS); + this.gl.linkProgram(this.shaderProgramSSS); + + this.shaderProgramSSSBlur = this.gl.createProgram(); + this.gl.attachShader(this.shaderProgramSSSBlur, vertexShaderQuad); + this.gl.attachShader(this.shaderProgramSSSBlur, fragmentShaderSSSBlur); + this.gl.linkProgram(this.shaderProgramSSSBlur); + this.shaderProgramForwardPass = this.gl.createProgram(); this.gl.attachShader(this.shaderProgramForwardPass, vertexShaderGeometry); this.gl.attachShader(this.shaderProgramForwardPass, fragmentShaderForwardPass); @@ -951,18 +1103,9 @@ App.Art.Engine = class { this.vertexNormalAttribute = this.gl.getAttribLocation(this.shaderProgramGeometry, "vertexNormal"); this.gl.enableVertexAttribArray(this.vertexNormalAttribute); - this.vertexNormalMorphAttribute = this.gl.getAttribLocation(this.shaderProgramGeometry, "vertexNormalMorph"); - this.gl.enableVertexAttribArray(this.vertexNormalMorphAttribute); - - this.vertexPositionMorphAttribute = this.gl.getAttribLocation(this.shaderProgramGeometry, "vertexPositionMorph"); - this.gl.enableVertexAttribArray(this.vertexPositionMorphAttribute); - this.vertexPositionAttribute2 = this.gl.getAttribLocation(this.shaderProgramShadow, "vertexPosition"); this.gl.enableVertexAttribArray(this.vertexPositionAttribute2); - this.vertexPositionMorphAttribute2 = this.gl.getAttribLocation(this.shaderProgramShadow, "vertexPositionMorph"); - this.gl.enableVertexAttribArray(this.vertexPositionMorphAttribute2); - this.textureCoordAttribute2 = this.gl.getAttribLocation(this.shaderProgramShadow, "textureCoordinate"); this.gl.enableVertexAttribArray(this.textureCoordAttribute2); @@ -971,6 +1114,12 @@ App.Art.Engine = class { this.quadTextureAttribute = this.gl.getAttribLocation(this.shaderProgramSSAO, "textureCoordinate"); this.gl.enableVertexAttribArray(this.quadTextureAttribute); + + this.quadPositionAttribute3 = this.gl.getAttribLocation(this.shaderProgramSSS, "vertexPosition"); + this.gl.enableVertexAttribArray(this.quadPositionAttribute3); + + this.quadTextureAttribute3 = this.gl.getAttribLocation(this.shaderProgramSSS, "textureCoordinate"); + this.gl.enableVertexAttribArray(this.quadTextureAttribute3); } bind(sceneData, sceneParams, dir) { @@ -1036,6 +1185,20 @@ App.Art.Engine = class { this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); } + if (V.setSSS) { + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.buffers.sssFBO); + this.gl.clear(this.gl.COLOR_BUFFER_BIT); + this.gl.useProgram(this.shaderProgramSSS); + this.drawSSS(sceneParams); + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); + + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.buffers.sssBlurFBO); + this.gl.clear(this.gl.COLOR_BUFFER_BIT); + this.gl.useProgram(this.shaderProgramSSSBlur); + this.drawSSSBlur(sceneParams); + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); + } + this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); this.gl.useProgram(this.shaderProgramForwardPass); this.drawForwardPass(sceneParams); @@ -1146,12 +1309,6 @@ App.Art.Engine = class { this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalBuffer[i]); this.gl.vertexAttribPointer(this.vertexNormalAttribute, 3, this.gl.FLOAT, false, 0, 0); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesMorphBuffer[i]); - this.gl.vertexAttribPointer(this.vertexPositionMorphAttribute, 3, this.gl.FLOAT, false, 0, 0); - - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalMorphBuffer[i]); - this.gl.vertexAttribPointer(this.vertexNormalMorphAttribute, 3, this.gl.FLOAT, false, 0, 0); - // bind materials per surface and set uniforms for (let j=0; j < modelParams.figures[i].surfaces.length; j++, count++) { if (!modelParams.figures[i].surfaces[j].visible) { @@ -1202,9 +1359,6 @@ App.Art.Engine = class { this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesPositionBuffer[i]); this.gl.vertexAttribPointer(this.vertexPositionAttribute2, 3, this.gl.FLOAT, false, 0, 0); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesMorphBuffer[i]); - this.gl.vertexAttribPointer(this.vertexPositionMorphAttribute2, 3, this.gl.FLOAT, false, 0, 0); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesTextureCoordBuffer[i]); this.gl.vertexAttribPointer(this.textureCoordAttribute2, 2, this.gl.FLOAT, false, 0, 0); @@ -1283,10 +1437,60 @@ App.Art.Engine = class { this.gl.drawElements(this.gl.TRIANGLES, 6, this.gl.UNSIGNED_SHORT, 0); } + drawSSS(sceneParams) { + this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgramSSS, "projection"), false, new Float32Array(this.matrixFlatten(this.buffers.matProj))); + + for (let i = 0; i < 32; ++i) { + this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramSSS, "samples[" + i + "]"), this.buffers.ssaoKernel[i]); + } + + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramSSS, "radius"), sceneParams.sss.radius); + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramSSS, "bias"), sceneParams.sss.bias); + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramSSS, "scale"), sceneParams.sss.scale); + + this.gl.activeTexture(this.gl.TEXTURE0); + this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.gPosition); + this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramSSS, "gPosition"), 0); + + this.gl.activeTexture(this.gl.TEXTURE1); + this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.gNormal); + this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramSSS, "gNormal"), 1); + + this.gl.activeTexture(this.gl.TEXTURE2); + this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.noiseTexture); + this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramSSS, "texNoise"), 2); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.quadPositionBuffer); + this.gl.vertexAttribPointer(this.quadPositionAttribute3, 2, this.gl.FLOAT, false, 0, 0); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.quadTextureBuffer); + this.gl.vertexAttribPointer(this.quadTextureAttribute3, 2, this.gl.FLOAT, false, 0, 0); + + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.buffers.quadIndexBuffer); + this.gl.drawElements(this.gl.TRIANGLES, 6, this.gl.UNSIGNED_SHORT, 0); + } + + drawSSSBlur(sceneParams) { + this.gl.activeTexture(this.gl.TEXTURE0); + this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.sssColorBuffer); + + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramSSSBlur, "blur"), sceneParams.sss.blur); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.quadPositionBuffer); + this.gl.vertexAttribPointer(this.quadPositionAttribute3, 2, this.gl.FLOAT, false, 0, 0); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.quadTextureBuffer); + this.gl.vertexAttribPointer(this.quadTextureAttribute3, 2, this.gl.FLOAT, false, 0, 0); + + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.buffers.quadIndexBuffer); + this.gl.drawElements(this.gl.TRIANGLES, 6, this.gl.UNSIGNED_SHORT, 0); + } + drawForwardPass(sceneParams) { // set scene uniforms this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sNormals"), sceneParams.settings.normals); this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sAO"), sceneParams.settings.ao); + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sIAO"), sceneParams.settings.iao); this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sSSAO"), V.setSSAO); this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sSSS"), sceneParams.settings.sss); this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sShadows"), V.setShadowMapping); @@ -1306,6 +1510,10 @@ App.Art.Engine = class { this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "metallic"), sceneParams.pbr.metallic); this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "fresnel"), sceneParams.pbr.fresnel); this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "ssaoInt"), sceneParams.ssao.intensity); + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssPower"), sceneParams.sss.power); + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssDistortion"), sceneParams.sss.distortion); + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssIntensity"), sceneParams.sss.intensity); + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssAmbient"), sceneParams.sss.ambient); for (let i = 0; i < sceneParams.directionalLights.length; i++) { let lightVect = this.polarToCart(this.degreeToRad(sceneParams.directionalLights[i].yr), this.degreeToRad(sceneParams.directionalLights[i].xr)); @@ -1360,12 +1568,6 @@ App.Art.Engine = class { this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalBuffer[i]); this.gl.vertexAttribPointer(this.vertexNormalAttribute, 3, this.gl.FLOAT, false, 0, 0); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesMorphBuffer[i]); - this.gl.vertexAttribPointer(this.vertexPositionMorphAttribute, 3, this.gl.FLOAT, false, 0, 0); - - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalMorphBuffer[i]); - this.gl.vertexAttribPointer(this.vertexNormalMorphAttribute, 3, this.gl.FLOAT, false, 0, 0); - // bind materials per surface and set uniforms for (let j=0; j < modelParams.figures[i].surfaces.length; j++, count++) { if (!modelParams.figures[i].surfaces[j].visible) { @@ -1423,6 +1625,10 @@ App.Art.Engine = class { this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.gShadowDepth); this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "textSampler[8]"), 8); + this.gl.activeTexture(this.gl.TEXTURE9); + this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.sssColorBufferBlur); + this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "textSampler[9]"), 9); + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "d"), mat.d); this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "Ka"), mat.Ka); this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "Ks"), mat.Ks); @@ -1435,10 +1641,7 @@ App.Art.Engine = class { this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "angle"), mat.transform[2]); this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "scale"), mat.transform[3]); - this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssPower"), sceneParams.sss.power); - this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssDistortion"), sceneParams.sss.distortion); - this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssIntensity"), sceneParams.sss.intensity); - this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssColor"), mat.Ksss); + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sss"), mat.sss); // draw materials this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, modelBuffers.verticesIndexBuffer[count]); @@ -1457,36 +1660,63 @@ App.Art.Engine = class { continue; } - let vertexPositionMorph = new Float32Array(modelBuffers.vertexCount[f]); - let vertexNormalMorph = new Float32Array(modelBuffers.vertexCount[f]); + let vertexPosition = new Float32Array(modelBuffers.vertexPositionBuffer[f]); + let vertexNormal = new Float32Array(vertexPosition.length); for (let m=0; m < modelParams.morphs.length; m++) { let morphValue = modelParams.morphs[m].value; if (morphValue !== 0) { let vp = modelBuffers.vertexPositionMorphs[f][m]; - let vn = modelBuffers.vertexNormalMorphs[f][m]; let vi = modelBuffers.vertexIndexMorphs[f][m]; if (morphValue === 1) { for (let j = 0; j < vi.length; j++) { - vertexPositionMorph[vi[j]] += vp[j]; - vertexNormalMorph[vi[j]] += vn[j]; + vertexPosition[vi[j]] += vp[j]; } } else { for (let j=0; j < vi.length; j++) { - vertexPositionMorph[vi[j]] += vp[j] * morphValue; - vertexNormalMorph[vi[j]] += vn[j] * morphValue; + vertexPosition[vi[j]] += vp[j] * morphValue; } } } } - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesMorphBuffer[f]); - this.gl.bufferData(this.gl.ARRAY_BUFFER, vertexPositionMorph, this.gl.STATIC_DRAW); + // recalculate normals + let indices = modelBuffers.figureIndices[f]; + for (let j=0; j < indices.length; j+=3) { + let idx1 = indices[j]*3; + let idx2 = indices[j+1]*3; + let idx3 = indices[j+2]*3; + + let v1 = [vertexPosition[idx1], vertexPosition[idx1+1], vertexPosition[idx1+2]]; + let v2 = [vertexPosition[idx2], vertexPosition[idx2+1], vertexPosition[idx2+2]]; + let v3 = [vertexPosition[idx3], vertexPosition[idx3+1], vertexPosition[idx3+2]]; + + let n = this.vectorNormalize(this.vectorCrossProduct(this.vectorSub(v2, v1), this.vectorSub(v1, v3))); + + vertexNormal[idx1] += n[0]; + vertexNormal[idx1+1] += n[1]; + vertexNormal[idx1+2] += n[2]; + vertexNormal[idx2] += n[0]; + vertexNormal[idx2+1] += n[1]; + vertexNormal[idx2+2] += n[2]; + vertexNormal[idx3] += n[0]; + vertexNormal[idx3+1] += n[1]; + vertexNormal[idx3+2] += n[2]; + } + + // fix edge normals + let seemMap = modelBuffers.figureSeemMaps[f]; + for (let j=0; j <= seemMap.length-2; j+=2) { + vertexNormal[seemMap[j]] = vertexNormal[seemMap[j+1]]; + } + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesPositionBuffer[f]); + this.gl.bufferData(this.gl.ARRAY_BUFFER, vertexPosition, this.gl.STATIC_DRAW); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalMorphBuffer[f]); - this.gl.bufferData(this.gl.ARRAY_BUFFER, vertexNormalMorph, this.gl.STATIC_DRAW); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalBuffer[f]); + this.gl.bufferData(this.gl.ARRAY_BUFFER, vertexNormal, this.gl.STATIC_DRAW); } modelBuffers.oldMorphValues = JSON.stringify(modelParams.morphs) + JSON.stringify(modelParams.figures); diff --git a/src/art/webgl/ui.js b/src/art/webgl/ui.js index 23a2290a7e8aad613e332e97320263f694f88960..f600afd7b036379ecbf24df7945d16275e696979 100644 --- a/src/art/webgl/ui.js +++ b/src/art/webgl/ui.js @@ -46,6 +46,7 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) { // canvas let cvs = document.createElement("canvas"); cvs.setAttribute("style", "position: absolute;"); + cvs.setAttribute("oncontextmenu", "return false;"); // btnLockView let btnLockView = document.createElement("input"); @@ -82,11 +83,18 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) { btnLockView.setAttribute("src", view.lockView ? lockViewDisabled : lockViewEnabled); } + let panY; + let zoomZ; + let zoomY; + let oz; + let oy; + let oxr; + // events btnInspectView.onclick = function(e){ updateLinkedButtons("inspect"); - view.yr = 180; + view.yr = 0; view.inspect = true; App.Art.Frame(slave, scene, view, p); render(); @@ -100,6 +108,7 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) { btnFaceView.onclick = function(e){ updateLinkedButtons("face"); + view.camera.x = 0; view.camera.y = p.height-5; view.yr = 0; view.camera.xr = -6; @@ -124,8 +133,25 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) { updateLinkedButtons("move"); - view.camera.y = view.camera.y + e.movementY/7*V.setPanSpeed; - view.yr = view.yr + e.movementX*5*V.setRotationSpeed; + if (e.buttons === 1) { // left click + panY = panY + e.movementY/7*V.setPanSpeed; + view.camera.y = view.camera.y + e.movementY/7*V.setPanSpeed; + view.yr = view.yr + e.movementX*5*V.setRotationSpeed; + } + if (e.buttons === 2) { // right click + view.yr = view.yr + e.movementX*5*V.setRotationSpeed; + view.camera.xr = view.camera.xr - e.movementY*1*V.setRotationSpeed; + view.camera.xr = Math.clamp(view.camera.xr, -89, 89); + + let r = (-oz-zoomZ)/Math.cos(oxr*Math.PI/180); + view.camera.y = oy + panY + zoomY - Math.sin((view.camera.xr)*Math.PI/180) * r + Math.sin(oxr*Math.PI/180)*r; + view.camera.z = Math.cos((view.camera.xr)*Math.PI/180) * -r; + } + if (e.buttons === 4) { // wheel click + panY = panY + e.movementY/7*V.setPanSpeed; + view.camera.y = view.camera.y + e.movementY/7*V.setPanSpeed; + view.camera.x = view.camera.x - e.movementX/7*V.setPanSpeed; + } render(); }; @@ -136,6 +162,12 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) { render(); return; } + oz = view.camera.z; + oy = view.camera.y; + oxr = view.camera.xr; + panY = 0; + zoomZ = 0; + zoomY = 0; e.preventDefault(); e.stopPropagation(); App.Art.isDraggingCanvas=true; @@ -163,10 +195,15 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) { // zoom speed based on distance from origin, and along direction of camera let zOld = view.camera.z; let magnitude = e.deltaY/(10/V.setZoomSpeed) * (-view.camera.z/50 + 0.2); + let zDistance = Math.cos(-view.camera.xr * (Math.PI/180)) * magnitude; view.camera.z -= zDistance; view.camera.z = Math.clamp(view.camera.z, -900, -10); - view.camera.y += Math.sin(-view.camera.xr * (Math.PI/180)) * magnitude * -(view.camera.z - zOld)/zDistance; + zoomZ -= zDistance; + + let yDistance = Math.sin(-view.camera.xr * (Math.PI/180)) * magnitude * -(view.camera.z - zOld)/zDistance; + view.camera.y += yDistance; + zoomY += yDistance; render(); return false; @@ -224,47 +261,6 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) { updateLinkedButtons(); render(); } - - /* - if (artSize === 3) { - let cvs2 = document.createElement("canvas"); - cvs2.setAttribute("style", "position: absolute; top: 530px; border: 4px; border-color: #151515; border-style: solid; margin-top: 15px;"); - - let cvs3 = document.createElement("canvas"); - cvs3.setAttribute("style", "position: absolute; top: 530px; left: 156px; border: 4px; border-color: #151515; border-style: solid; margin-top: 15px;"); - - let oldYr = view.transform.yr; - // let oldCamera = view.camera; - - view.camera.y = p.height * 8 / 10; - view.camera.z = -65 - (Math.sqrt(slave.boobs)/10); - - cvs2.width = 150; - cvs2.height = 150; - view.rwidth = cvs2.width * V.setSuperSampling; - view.rheight = cvs2.height * V.setSuperSampling; - App.Art.engine.render(scene, cvs2); - - view.camera.y = p.height * 5.5 / 10; - view.camera.z = -65; - view.transform.yr = -180; - - cvs3.width = 150; - cvs3.height = 150; - view.rwidth = cvs3.width * V.setSuperSampling; - view.rheight = cvs3.height * V.setSuperSampling; - App.Art.engine.render(scene, cvs3); - - container.appendChild(cvs2); - container.appendChild(cvs3); - - App.Art.Frame(slave, scene, view, p); - - view.rwidth = cvs.width * V.setSuperSampling; - view.rheight = cvs.height * V.setSuperSampling; - view.transform.yr = oldYr; - // view.camera = oldCamera; - }*/ }; App.Art.Frame = function(slave, scene, view, p) { @@ -308,11 +304,13 @@ App.Art.AutoFrame = function(view, slaveHeight, cameraHeight, window, offset) { view.camera.z = -h; view.camera.y = cameraHeight; + view.camera.x = 0; view.camera.xr = -rot; }; App.Art.FixedFrame = function(view) { view.camera.z = -282; view.camera.y = 123; + view.camera.x = 0; view.camera.xr = -6; }; diff --git a/src/gui/options/options.js b/src/gui/options/options.js index 38f0c8a7d5a50de9e5d74ac9342735b9b0ae3b34..95170f9e8cc052facbf22bf520fe12ca6d9712a4 100644 --- a/src/gui/options/options.js +++ b/src/gui/options/options.js @@ -1167,7 +1167,7 @@ App.UI.artOptions = function() { options.addOption("Clothing erection bulges are", "showClothingErection") .addValue("Enabled", true).on().addValue("Disabled", false).off(); } else if (V.imageChoice === 4) { - options.addComment(`This art is currently (12/12/21) the most actively developed. Real time 3D models. <a href='https://mega.nz/folder/m1JQ3JAQ#2GJsM7csBBvBu0DX8SB2kA' target='_blank'> Download the WebGL art assets</a> and place the 'webgl' folder into the resources/ folder where this HTML file is. + options.addComment(`This art is currently (05/19/22) the most actively developed. Real time 3D models. <a href='https://mega.nz/folder/DtoyiSwC#5FLXDYr1Uy90PZjxmfrKXA' target='_blank'> Download the WebGL art assets</a> and place the 'webgl' folder into the resources/ folder where this HTML file is. Then <b>refresh</b> the page. Create the resources folder if it does not exist. <span class="warning">(Android/MacOS not supported)</span>`); @@ -1203,6 +1203,8 @@ App.UI.artOptions = function() { .addComment("Recommended to be between 0.5 and 1.5."); options.addOption("Ambient Occlusion", "setSSAO") .addValue("Enabled", true).on().addValue("Disabled", false).off(); + options.addOption("SubSurface Scattering", "setSSS") + .addValue("Enabled", true).on().addValue("Disabled", false).off(); options.addOption("Shadowmapping", "setShadowMapping") .addValue("Enabled", true).on().addValue("Disabled", false).off(); options.addOption("Tonemapping", "setTonemapping")