{ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://raw.githubusercontent.com/hakasapl/xpmf/main/schema/profile.schema.json", "title": "XPMF profile", "description": "One XPMF profile: which single pass material objects it is about, and what is done for them and for the shapes of the statics that carry them. One profile per file in Data/SKSE/Plugins/XPMF/*.json. A material object that several profiles match belongs to the one whose matching pattern is most specific (most literal characters, then fewest wildcards), then to a pbr profile over a general one, then to the first file name in alphabetical order. Only name and editorIds are required. A file with a wrongly typed or out-of-range value is rejected as a whole, with every reason in one log line; a key that is not a setting is ignored with a warning.", "type": "object", "required": ["name", "editorIds"], "additionalProperties": false, "properties": { "$schema": { "type": "string", "description": "Lets an editor validate the file against this schema. Ignored by XPMF." }, "comment": { "type": "string", "description": "A note for whoever reads the file. Ignored by XPMF." }, "name": { "type": "string", "minLength": 1, "description": "The profile's name, for the log. Two files may share one; the log adds the file name." }, "editorIds": { "$ref": "#/definitions/patternList", "description": "Which material objects the profile is about: patterns over their EditorIDs. Every vanilla snow material contains \"Snow\" (\"*snow*\" takes them all), every vanilla ash material starts with \"AshMaterial\", \"DLC2AshMaterial\" or \"AshLODMaterial\". An empty list matches nothing." }, "excludeEditorIds": { "$ref": "#/definitions/patternListOrNull", "description": "Material objects that match one of these as well are not the profile's. None by default." }, "pbr": { "type": "boolean", "default": false, "description": "Only material objects Community Shaders' True PBR has a configuration for (Data/PBRMaterialObjects/.json), and the profile's textures are PBR ones. A pbr profile is the one kind that patches such a record; any other profile leaves such a record untouched and only treats its shapes." }, "diffuseTexture": { "$ref": "#/definitions/texture", "description": "The texture the projection shows instead of the game's ProjectedDiffuse: usually the landscape's own snow or ash texture, so that a covered object matches the ground next to it. Empty, null, absent, or the game's own path = not replaced." }, "normalTexture": { "$ref": "#/definitions/texture", "description": "Its normal map, instead of the game's ProjectedNormal. Empty, null, absent, or the game's own path = not replaced." }, "noiseTexture": { "$ref": "#/definitions/texture", "description": "The coverage noise, instead of the game's ProjectedNoise. Empty, null, absent, or the game's own path = not replaced." }, "detailNormalTexture": { "$ref": "#/definitions/texture", "description": "The detail normal, instead of the game's ProjectedNormalDetail. Empty, null, absent, or the game's own path = not replaced." }, "falloffScale": { "type": ["number", "null"], "default": null, "description": "The material objects' falloff scale: how far the coverage noise raises what a surface has to face up by, the patches the projection is missing from. 0.25 to 0.5 on the vanilla snow materials. null or absent = as each record has it." }, "falloffBias": { "type": ["number", "null"], "default": null, "description": "The material objects' falloff bias: what a surface has to face up by before the noise, how much of it is covered at all. 0.4 on the vanilla snow materials, up to 0.82 on the \"Light\" ones that are a dusting. null or absent = as each record has it." }, "noiseUVScale": { "type": ["number", "null"], "exclusiveMinimum": 0, "default": null, "description": "The material objects' noise UV scale: world units per tile of the coverage noise, and, the shader tiling the projected textures at a fixed ratio to it, of the profile's textures. 20 to 1500 on the vanilla materials. null or absent = as each record has it." }, "maxAngle": { "type": ["number", "null"], "minimum": 0, "maximum": 180, "default": null, "description": "The max angle, in degrees, written into every static that carries one of the profile's patched material objects: the projection needs a surface to face up by more than the cosine of it before the noise has its say. Vanilla statics use 30 to 120 (the Creation Kit's \"30-90\" is only a hint; Nordic ruins and word walls sit at 120). null or absent = each static's own." }, "lodIdentifier": { "type": ["string", "null"], "default": null, "description": "What the LOD generator names the object LOD shapes of the profile's statics, the way the game's own are named objSnow and objAsh: this name for the shapes with LOD textures, this name with HD appended for the ones the generator gives full textures (the nearest level), and either may go on with a dash and whatever the generator puts there (-LargeRef on the shapes of large references); any other character after the name makes it some other name. Case does not matter. null or absent = \"obj\" and the profile's name (objMoss for a profile named moss). objSnow and objAsh, with the same endings, are the game's own and are left to it." }, "lodMaterial": { "type": ["string", "null"], "default": null, "description": "EditorID of the material object the shapes carrying the profile's lodIdentifier are given in object LOD, the way the game gives its objSnow shapes SnowLODMaterial. The record is the profile's own whatever the patterns say, and wants its own falloff and noise UV scale tuned for distance (SnowLODMaterial uses a noise UV scale of 1500 where SnowMaterialObject1P uses 48); DynDOLOD, reading the profile, names the shapes it builds for the profile's statics with the identifier once a profile has this. null or absent = the profile's statics get no projection in object LOD beyond what the game does for snow and ash on its own. A pbr profile may name one too; which of two profiles naming the same record has the record itself follows the same rules as for patterns, and whether True PBR has a configuration for it makes no difference to object LOD, which Community Shaders' PBR never reaches." }, "lodMaterialHD": { "type": ["string", "null"], "default": null, "description": "The same for the HD shapes (full textures, the nearest level), the way SnowLODMaterialHD serves objSnowHD; which shapes are HD and the vertex alpha intensity on them are the generator's call. Does nothing without lodMaterial. null or absent = the HD shapes get lodMaterial as well." }, "neutralizeVertexColors": { "type": "boolean", "default": true, "description": "Whether shapes that carry the projection get white vertex colors where snow can land, so that the colors a mesh's author painted (usually baked shadow) do not darken the snow. Vertices the snow cannot reach (facing sideways or down where the object stands, or under cover) keep the mesh's colors: baked shading on cliff faces, the fade to black down a cave or mine entrance. Measures cover even with roofShelter off." }, "neutralizeVertexColorsSkip": { "$ref": "#/definitions/patternListOrNull", "description": "Patterns over the EditorIDs of the statics (base records) whose shapes keep their vertex colors all the same. None by default." }, "neutralizeVertexAlpha": { "type": "boolean", "default": true, "description": "Whether shapes that carry the projection start from a vertex alpha of 1, discarding a mask the mesh's author painted against the game's own projection, rather than from the mesh's own. Never on a shape whose alpha is transparency." }, "neutralizeVertexAlphaSkip": { "$ref": "#/definitions/patternListOrNull", "description": "Patterns over the EditorIDs of the statics whose shapes keep the mesh's vertex alpha all the same, for a mask that is wanted (the farmhouse walkways of Better Dynamic Snow, say). None by default." }, "roofShelter": { "type": "boolean", "default": true, "description": "Whether the vertex alpha is multiplied down to keep the projection out from under cover: roofs, overhangs, anything a static puts over another." }, "roofShelterSkip": { "$ref": "#/definitions/patternListOrNull", "description": "Patterns over the EditorIDs of the statics whose shapes stay covered under a roof. None by default." }, "roofShelterFixVertices": { "type": "boolean", "default": true, "description": "Whether a shape partly under cover is given extra vertices where the cover changes. The shelter reaches the shader through vertex alpha, which runs straight between two vertices, and game meshes are coarse where it counts: a walkway floor is a row of planks with a vertex at either end, so the fade is interpolated across the whole bay, a wedge of snow reaching in under the eave. With this on, XPMF splits, on its worker thread, the edges of such triangles where the interpolation is visibly off - along the drip line and through the fade, nowhere else, the way hand-fixed meshes place their loops - and gives the shape a vertex buffer with those vertices. Only shapes that turn out partly covered are touched; a shape whose vertex alpha is transparency, or that draws LOD prefixes of its triangles, is left as it is. Does nothing without roofShelter." }, "shelterFade": { "type": "number", "minimum": 0, "maximum": 128, "default": 64, "description": "World units over which the projection fades out under cover: 0 is a hard edge at the drip line, 64 about how far wind carries snow in under an eave. Whatever the material, the projection is visibly half gone half way through." }, "shelterRandomness": { "type": "number", "minimum": 0, "maximum": 128, "default": 32, "description": "World units the snow's edge under cover swings in and out along an eave at most, so that it is not a straight line: waves of random depth, about a hundred units apart, the deepest close to this far either way from where the fade puts the edge (half way through shelterFade). 0 keeps the edge straight. The edge never comes out past the drip line: a swing towards it eases off short of it. The waves follow smooth noise over the ground, the same for every object at a spot, and are carried by the vertices the shape has (with roofShelterFixVertices, the ones it adds along the drip line): no vertices are added for them. Does nothing without roofShelter." }, "waterFade": { "type": "boolean", "default": false, "description": "Whether the projection is kept off whatever lies under water: below the water level of the cell it stands in (the sea, a lake that fills a cell) or under a placed water plane (rivers, ponds, streams). It fades out across the surface over 24 units: visibly half gone at the surface itself, gone 12 units below it, as it was 12 units above it. Works through the vertex alpha like roofShelter, and on its own: with roofShelter and neutralizeVertexAlpha off as well as on, the least of what each leaves a vertex counting. Exterior cells only. A shape whose vertex alpha is transparency cannot be masked part by part and loses the projection as a whole once half of what could hold it lies under water." }, "waterFadeRandomness": { "type": "number", "minimum": 0, "maximum": 64, "default": 16, "description": "World units the projection's edge at a water surface is lifted by at most, so that the waterline is not a straight line: waves of random height along it, as if splash and spray reached up unevenly, from the same smooth noise over the ground as shelterRandomness. Only ever upward: where a wave would take the edge under the water it eases down onto the surface instead, so the projection never shows under water. 0 keeps the edge on the surface. No vertices are added for it. Does nothing without waterFade." }, "specularMult": { "type": ["number", "null"], "minimum": 0, "maximum": 10, "default": null, "description": "What the specular strength of every shape the projection is on is multiplied by, on a copy of the shape's material: 0 takes the highlight off, 1 leaves it. The shader lights covered pixels with the mesh's own specular, which is how a glossy mesh makes glossy snow; the snow rim light that would replace it belongs to the game's improved snow pass, which XPMF holds off. Not applied to Community Shaders' True PBR materials, which keep their roughness scale in that field; PBR snow takes its roughness and specular from CS's PBRMaterialObjects configuration. null or absent = as the mesh has it." } }, "definitions": { "pattern": { "type": "string", "description": "A wildcard pattern matched against a whole EditorID, case insensitive: * stands for any run of characters, ? for any one; without either it is an exact EditorID. Anchor short words: \"*ash*\" is also in splash, trash and wash. Blank entries are dropped." }, "patternList": { "type": "array", "items": { "$ref": "#/definitions/pattern" } }, "patternListOrNull": { "oneOf": [ { "$ref": "#/definitions/patternList" }, { "type": "null" } ] }, "texture": { "type": ["string", "null"], "description": "A texture path relative to Data, in any case, with any slashes, with or without a leading textures/ and a trailing .dds: \"textures/landscape/snow01.dds\", \"landscape\\\\snow01\" and \"Data/textures/landscape/snow01.dds\" all name the same file. Loose files, archives and a mod manager's virtual folder all work." } } }