From 8c1b3704de8171adb45e2ce36d1dcc4c8bfd9eb1 Mon Sep 17 00:00:00 2001 From: TWG Date: Mon, 22 Jul 2024 10:39:41 +0200 Subject: [PATCH 1/3] LandControl + ring detail documentation --- .../syntax/PQSMods/LandControl/LandClasses.md | 161 +++++++++--- .../syntax/PQSMods/LandControl/LandControl.md | 69 +++-- .../PQSMods/LandControl/LandControlLanding.md | 12 +- .../LandControl/ScatterMaterialType.md | 246 +++++++++++++++++- .../syntax/PQSMods/LandControl/Scatters.md | 89 +++++-- wwwroot/content/syntax/Rings.md | 224 +++++++++++++++- 6 files changed, 717 insertions(+), 84 deletions(-) diff --git a/wwwroot/content/syntax/PQSMods/LandControl/LandClasses.md b/wwwroot/content/syntax/PQSMods/LandControl/LandClasses.md index 5ec44f6..dd31aa6 100644 --- a/wwwroot/content/syntax/PQSMods/LandControl/LandClasses.md +++ b/wwwroot/content/syntax/PQSMods/LandControl/LandClasses.md @@ -1,8 +1,10 @@ -`LandClasses` are regions specified by the LandControl PQSMod that can locally customize several features of the PQS, including features from other PQSMods like HeightColorMap or noise PQSMods. LandClasses can change terrain height, change terrain color, add color noise and height noise, and add ground scatters. LandClasses are defined via ranges of altitude, latitude, and longitude. +`LandClasses` are regions specified by the LandControl PQSMod that can locally customize several features of the local terrain. To put it very, very simply: LandControl's LandClasses are what you get when you take the concept of [HeightColorMap]( /Syntax/PQSMods/HeightColorMap) and empower it with greater control over local colors, the ability to place detail objects called terrain scatters (examples are grass, trees and boulders) and even an ability to adjust the local elevation of the terrain. + +Similarly to HeightColorMap, the LandClasses are limited by altitude, but unlike HeightColorMap, LandControl also lets you limit the latitude and longitude range(s) wherein a LandClass should act. ## Subnodes {#Subnodes} - Both are defined under the main table. -* LerpRange { } = Defines range values -* Scatters { } = Defines used scatter amounts +* LerpRange { } = Defines the range along the given coordinate axes wherein a LandClass should act. +* Scatters { } = Defines the terrain scatters that may spawn in this LandClass, and the prominence of each. ## Example {#Example} ``` @@ -22,7 +24,7 @@ LandControl coverageSeed = 234124 name = IceCaps latDelta = 0 - latitudeFloat = True + latitudeDouble = True lonDelta = 1 minimumRealHeight = 20 noiseBlend = 0.25 @@ -45,7 +47,7 @@ LandControl startEnd = -10 startStart = -10 } - latitudeFloatRange + latitudeDoubleRange { endEnd = 11 endStart = 11 @@ -73,36 +75,83 @@ LandControl } ``` +## Parameters {#Parameters} +Similar to the article on the base mod, we'll separate the parameters for a LandClass into categories: +1. **Coverage Control**: parameters that apply further control to the coverage of a LandClass. +2. **Color Control**: parameters that specify the colors that a LandClass may apply to the terrain. +3. **Elevation Control**: parameters that control the local elevation of terrain that falls under the given LandClass. +4. **Range Control**: parameters that specify the range of altitudes, latitudes and longitudes that the LandClass should cover. + +One parameter does not fall into any of these categories: `name`, which is useful in distinguishing LandClasses. Naming LandClasses is up to user preference, pick any name that seems applicable and intuitive. + +We shall now discuss each category in turn. + +(Note: the parameters `latDelta` and `lonDelta` also appear in exports from Kittopia. These are used internally by KSP and should not actually be set from config files. **Ignore these.**) + +## Coverage Control {#Coverage_Control} +Similar to how the base mod applies a Simplex noise to the altitude, latitude and longitude, each LandClass may apply a Simplex noise to the computed coverage. + +To be specific, LandControl first obtains a normalized value for altitude, latitude and longitude from the input vertex. It then applies a simplex noise to each of these coordinates just to shake things up a little and avoid obvious 'zones'. + +The resulting shifted coordinates are then compared to the altitude, latitude and longitude ranges specified in each LandClass. The per-coordinate-axis contributions are multiplied together to get the overall **coverage** of the LandClass. A simplex noise can be applied to this coverage **multiplicatively** for further detail. The below parameters control this noise. + +For a more detailed explanation of simplex noise parameters, see [the main mod article]( /Syntax/PQSMods/LandControl/LandControl#Noise_Control). + |Property|Format|Description| |--------|------|-----------| -|name|Text|The name of the LandClass.| -|alterApparentHeight|Decimal|Supposedly adjusts the terrain's appearance. Only ever observed in the practice of forming icecaps.| -|alterRealHeight|Decimal|Supposedly adjusts the terrain's actual height.| -|minimumRealHeight|Decimal|The minimum height of the LandClass's terrain.| -|color|Color|The color of the region.| -|coverageBlend|Decimal|The blend of the coverage with surrounding LandClasses.| -|coverageFrequency|Decimal|The size of the each feature of the LandClass coverage. As frequency gets bigger, size gets smaller.| -|coverageOctaves|Integer|The amount of blanketing over the LandClass coverage. Higher octaves mean rougher coverage.| -|coveragePersistance|Decimal|The complexity of or amount of detail in the LandClass coverage.| -|coverageSeed|Integer|The random seed of the LandClass coverage.| -|noiseBlend|Decimal|The blend of the LandClass noise with adjacent terrain.| -|noiseFrequency|Decimal|The size of the each feature of the LandClass noise. As frequency gets bigger, size gets smaller.| -|noiseOctaves|Integer|The amount of blanketing over the LandClass noise. Higher octaves mean rougher noise.| -|noisePersistance|Decimal|The complexity of or amount of detail in the LandClass noise.| -|noiseSeed|Integer|The random seed of the LandClass noise.| -|noiseColor|Color|The main color of the noise to be added to the LandClass.| -|latDelta|Decimal|The change between min and max of ~~the latitude specified.~~ 0 latitude?| -|latitudeDouble|Boolean|Whether to use a second latitude range - could be used for mirroring over the equator.| -|lonDelta|Decimal|The change between min and max of ~~the longitude specified.~~ 0 longitude?| -|altitudeRange|LerpRange|Determines the heights at which the LandClass encompasses.| -|latitudeRange|LerpRange|Determines the latitudes at which the LandClass encompasses.| -|latitudeFloatRange|LerpRange|Determines the second latitudes at which the LandClass encompasses - only used if `latitudeFloat` is true.| -|longitudeRange|LerpRange|Determines the longitudes at which the LandClass encompasses.| - -## LerpRange {#LerpRange} -Each `LerpRange { }` node describes a range of numbers to encompass, or lerp over. These ranges are applied to each dimension. The image below describes the valid ranges for latitude and longitude, with a handy diagram at the bottom for a visual description of the coverage of the LandClass over a Float "dimension." Areas where coverage is not complete are determined by the `coverage___` properties. - -![alttext](https://media.discordapp.net/attachments/717082915565076491/717506199100194876/LANDCONTROL.png) +|coverageBlend|Decimal|The strength of the simplex noise. This linearly scales the strength of the simplex noise multiplication.| +|coverageFrequency|Decimal|The initial size of the simplex noise.| +|coverageOctaves|Integer|The number of simplex noise iterations.| +|coveragePersistance|Decimal|The multiplicative falloff of noise strength for each successive octave.| +|coverageSeed|Integer|A seed for the simplex noise generator, to produce different patterns.| + +## Color Control {#Color_Control} + +Unlike HeightColorMap, which only allows for a single color value per region, LandControl allows for two color values, +between which it interpolates with yet another Simplex noise. + +|Property|Format|Description| +|--------|------|-----------| +|color|Color|The main color of the region.| +|noiseColor|Color|A secondary color for the region.| +|noiseBlend|Decimal|A multiplicative strength value for the Simplex noise.| +|noiseFrequency|Decimal|The size of the color noise. As frequency gets bigger, size gets smaller.| +|noiseOctaves|Integer|The number of color noise octaves.| +|noisePersistance|Decimal|The falloff of successive noise iterations.| +|noiseSeed|Integer|The seed of the simplex noise.| + +## Elevation Control {#Elevation_Control} + +As an extra luxury, LandControl also allows for limited adjustments to local elevation. + +|Property|Format|Description| +|--------|------|-----------| +|alterApparentHeight|Decimal|LandControl writes the normalized altitude to the alpha channel of the vertex color. Should you want to, then you can use this parameter to adjust the normalized altitude within this LandClass, but only insofar as the vertex color is concerned.| +|alterRealHeight|Decimal|An absolute offset (IE it is applied additively) to the terrain elevation within this LandClass, scaled by the coverage of the LandClass.| +|minimumRealHeight|Decimal|**If not set to zero**: if the altitude of the input vertex relative to sea level is less than this value (yes, this value describes a sea level relative elevation) then the vertex' elevation is raised to this value, depending on the LandClass coverage.| + +## Range Control {#Range_Control} + +At first glance the parameters for a **LerpRange** may seem a bit confusing. In essence, Kerbal Space Program performs a double linear interpolation. + +Think of each LerpRange one at a time. For example, picture in your head a 2D grid, like a map. The horizontal axis of that map will be the planet's longitude, progressing from zero on the left, to one on the right. Similarly, the vertical axis represents latitude, from zero at the bottom to one at the top. As with a heightmap, you can imagine the brightness of points in the grid as the elevation. + +Each LerpRange essentially defines a region along one of these axes. Let's take the horizontal or 'longitude' axis. The parameters supplied to `longitudeRange` will create a vertical 'band' in the 2D grid where intensity increases from zero to one, and then from one to zero again. + +The same will apply to the latitude and the altitude, and the product of these three 'bands' is the coverage - changes from the coverage noise notwithstanding. + +Note that the latitude actually specifies two ranges: `latitudeRange` and `latitudeDoubleRange`. This second range goes unused unless the `latitudeDouble` parameter is set to True. It is used to create a second latitude range for this LandClass. KSP will take the largest value of the two latitude ranges as the coverage for that coordinate axis. A notable use of this feature is the polar ice caps of Kerbin. + +|Property|Format|Description| +|--------|------|-----------| +|latitudeDouble|Boolean|Whether to use a second latitude range. Example use case: mirroring over the equator.| +|altitudeRange|LerpRange|Determines the heights at which the LandClass has dominion.| +|latitudeRange|LerpRange|Determines the latitudes at which the LandClass has dominion.| +|latitudeDoubleRange|LerpRange|Optionally, a second latitude range over which the LandClass has dominion. Only used if `latitudeDouble` is true.| +|longitudeRange|LerpRange|Determines the longitudes at which the LandClass has dominion.| + +## LerpRange {#Lerp_Range} +Each `LerpRange { }` node describes a range of numbers to encompass, or lerp over. They contain four values: `startStart`, `startEnd`, `endStart` and `endEnd`. These names a perhaps a bit confusing so let's discuss them in detail. |Property|Format|Description| |--------|------|-----------| @@ -112,9 +161,49 @@ Each `LerpRange { }` node describes a range of numbers to encompass, or lerp ove |endEnd|Decimal|The true end of the LandClass coverage. Coverage before this point is sparse, while coverage after this point is non-existent.| ## Scatters {#Scatters} -Although not a true scatters node, the `Scatters { }` node in a LandClass node has a list of values in which each modifies the density of the scatter's use in the LandClass. +LandClasses may specify which terrain scatters may spawn in the area that they have dominion over, as well as a relative density for these whitelisted terrain scatters. This takes the form of **one** `Scatters { }` node containing `Value { }` nodes. + +Each `Value { }` node references a terrain scatter from the main `Scatters { }` (as defined under in the LandControl node) by name. Example: + +``` +LandControl +{ + // The 'main' scatters node. This DEFINES the terrain scatters. + Scatters + { + Value + { + name = Example_Scatter + } + } + LandClasses + { + Value + { + name = Example_LandClass + // This REFERENCES the defined terrain scatters. + Scatters + { + Value + { + // Reference defined scatters by name. + scatterName = Example_Scatter + density = 0.5 + } + } + } + } +} +``` |Property|Format|Description| |--------|------|-----------| -|density|Decimal|The amount to modify the scatter's density with. Seems to be multiplied with the scatter's `maxScatter`?| -|scatterName|Text|The name of the scatter to modify the density of.| +|density|Decimal|The relative density for this terrain scatter.| +|scatterName|Text|The name of the scatter to spawn in this LandClass.| + +### How precisely does density work? {#How_Does_Density_Work} +LandControl first checks which LandClasses are relevant to a given vertex. It keeps track of these and their contribution values, which it turns into a weighted sum. + +Later, if spawning scatters is requested, it tracks density per terrain scatter. For some, this will be zero. The density value of a LandClass' terrain scatter reference is added to a terrain scatter's density tally, multiplied by the weighted contribution of the LandClass, as well as the terrain scatter density as set by the player in the game's settings. + +Finally, terrain scatters are created using the sum density for each scatter, which gets multiplied with their `densityFactor` and `maxScatter` parameters. This seems to form the basis for whatever scatter distribution system KSP uses. More info on the parameters for terrain scatters themselves can be found in the [main article on terrain scatters]( /Syntax/PQSMods/LandControl/Scatters). diff --git a/wwwroot/content/syntax/PQSMods/LandControl/LandControl.md b/wwwroot/content/syntax/PQSMods/LandControl/LandControl.md index 8180696..a909821 100644 --- a/wwwroot/content/syntax/PQSMods/LandControl/LandControl.md +++ b/wwwroot/content/syntax/PQSMods/LandControl/LandControl.md @@ -53,25 +53,54 @@ PQS } ``` +## How it works {#How_it_works} +When it is LandControl's turn to affect the color and elevation of a planet's surface, it obtains three coordinates from the data of the vertex being built: the altitude, the latitude and the longitude. Each of these is **normalized**: that is to say, it is zero at least and one at most. + +LandControl then loops over all defined [LandClasses]( /Syntax/PQSMods/LandControl/LandClasses) and, using the values for altitude, latitude and longitude, evaluates several range intervals. In essence, since land classes are defined along a certain altitude, latitude and longitude range, LandControl finds the extent to which each LandClass has dominion over the vertex that is being processed at that moment. This is the '**coverage**' of a LandClass, though perhaps a more apt term is 'contribution', as it determines the extent to which a given LandClass contributes to the final result for a given vertex. **The sum of contributions of LandClasses for a vertex is always one.** + +Depending on their relative contribution, LandControl may then add [terrain scatters]( /Syntax/PQSMods/LandControl/Scatters), adjust the terrain elevation, and color the terrain. **Each of these changes is scaled by the LandClass' relative contribution to that vertex.** + +## Parameters {#Parameters} + +The base parameters for LandControl can be distinguished into three categories: +1. **Noise Control**: parameters for simplex noise that is applied to each of the three input coordinates: normalized altitude, latitude and longitude. +2. **Altitude Control**: parameters that specify how LandControl should obtain the normalized altitude coordinate (latitude and longitude are calculated from the position on the sphere). +3. **Permission Control**: parameters that specify what exactly you do or don't allow LandControl to modify. +We shall discuss each category in sequence. + +### Noise Control {#Noise_Control} +It would look a bit plain if the LandClasses were purely linear (see HeightColorMap for example). To this end, LandControl __adds__ a simplex noise to the three coordinates (altitude, latitude, longitdue) We shall discuss each noise parameter in general, not specifically for each coordinate, because the per-coordinate behavior is mostly identical. + +In other words, the explanation for 'Blend' applies to 'altitudeBlend', 'latitudeBlend' and 'longitudeBlend'. Furthermore, each of the properties below exists for all three coordinate types, prefixed similarly by the coordinate's name, so 'Frequency' is the explanation for the properties 'altitudeFrequency', 'latitudeFrequency' and 'longitudeFrequency'. + +|Property|Format|Description| +|--------|------|-----------| +|Blend|Decimal|The strength of the noise. Is applied multiplicative to the noise output before being added to the respective coordinate.| +|Frequency|Decimal|The higher the frequency, the smaller the period of the noise, resulting in more densely packed 'peaks'.| +|Octaves|Integer|The simplex being evaluated is a 'gradient noise'. This means that multiple iterations or '**octaves**' of the noise are applied on top of each other to add more detail.| +|Persistance|Decimal|Controls the strength of each successive octave. Another way to look at it is that it controls the falloff, IE if persistance equals 0.5 then the first octave has strength 1, the second octave has strength 0.5, the third has strength 0.25, the fourth has 0.125, etc.| +|Seed|Integer|Each seed results in a (typically unique) noise pattern, in the same way that each integer value is similar yet distinct.| + +### Altitude Control {#Altitude_Control} +These offer control over the way the altitude coordinate is obtained: is it sampled from a specified heightmap, or calculated from the current (taking PQSMod order into account) altitude of the given terrain vertex? + +You can provide LandControl with a heightmap to sample for each vertex as a source for the normalized height. In this case, the brightness of the heightmap pixel at a given vertex' position is taken as the normalized height. If no heightmap is specified, or if the use thereof is disabled through the use of `useHeightMap`, then LandControl must calculate the normalized height from the given vertex, by looking at the displacement applied by previous PQSMods. Initially, the created terrain vertex will be at the surface of a sphere, and preceding PQSMods may have shifted it upward or downward to create mountains, valleys, canyons, craters, etc. But LandControl does not know in advance by how much the terrain will at most be moved. + +Though theoretically this can be calculated through analyzing the possible minimum and maximum output of each PQSMod, a far simpler option - and one that gives more control - is to specify a maximum altitude. This is the function of `vHeightMax`: it specifies the altitude (relative to sea level) that LandControl should regard as having a normalized altitude of 1. + +The math is then: `normalized_altitude = (distance_from_planet_center - planet_radius) / vHeightMax` + +|Property|Format|Description| +|--------|------|-----------| +|heightMap|File Path|Optional parameter. If set, and if `useHeightMap` is **True**, then LandControl samples the specified heightmap for each vertex to obtain the normalized altitude coordinate.| +|useHeightMap|Boolean|If **True**, the specified height map is used as the source for the normalized altitude coordinate.| +|vHeightMax|Decimal|If the heightmap is not set, or if `useHeightMap` is **False**, then LandControl calculates the normalized altitude from the given vertex' elevation relative to `vHeightMax`. In other words, this parameter specifies the elevation relative to sea level at which the normalized altitude is 1.| + +### Permission Control {#Permission_Control} + |Property|Format|Description| |--------|------|-----------| -|createColors|Boolean|Whether to use/affect colors.| -|createScatters|Boolean|Whether to create scatters.| -|heightMap|File Path|Use currently unknown - could be using it as a mask?| -|useHeightMap|Boolean|Whether to use the height map for...?| -|vHeightMax|Decimal|The max height for the height map?| -|altitudeBlend|Decimal|The blend amount with adjacent terrain.| -|altitudeFrequency|Decimal|The size of the each feature of the terrain noise. As frequency gets bigger, size gets smaller.| -|altitudeOctaves|Integer|The amount of blanketing over the noise. Higher octaves mean rougher noise.| -|altitudePersistance|Decimal|The complexity of or amount of detail in the noise.| -|altitudeSeed|Integer|The random seed of the noise.| -|latitudeBlend|Decimal|The blend amount with adjacent terrain.| -|latitudeFrequency|Decimal|The size of the each feature of the terrain noise. As frequency gets bigger, size gets smaller.| -|latitudeOctaves|Integer|The amount of blanketing over the noise. Higher octaves mean rougher noise.| -|latitudePersistance|Decimal|The complexity of or amount of detail in the noise.| -|latitudeSeed|Integer|The random seed of the noise.| -|longitudeBlend|Decimal|The blend amount with adjacent terrain.| -|longitudeFrequency|Decimal|The size of the each feature of the terrain noise. As frequency gets bigger, size gets smaller.| -|longitudeOctaves|Integer|The amount of blanketing over the noise. Higher octaves mean rougher noise.| -|longitudePersistance|Decimal|The complexity of or amount of detail in the noise.| -|longitudeSeed|Integer|The random seed of the noise.| +|createColors|Boolean|If true, then LandControl will **replace** the terrain colors using the weighted local contribution of the LandClasses.| +|createScatters|Boolean|If true, then LandControl will (attempt to) place terrain scatters. The scatters placed in an area depend on the applicable LandClasses and their densities are scaled by the relative contribution of each LandClass.| +|order|Integer|The order in the mod stack to apply LandControl at.| +|enabled|Boolean|If set to **False** then LandControl is skipped and does not contribute to the appearance of the terrain.| diff --git a/wwwroot/content/syntax/PQSMods/LandControl/LandControlLanding.md b/wwwroot/content/syntax/PQSMods/LandControl/LandControlLanding.md index a660e7f..7e06746 100644 --- a/wwwroot/content/syntax/PQSMods/LandControl/LandControlLanding.md +++ b/wwwroot/content/syntax/PQSMods/LandControl/LandControlLanding.md @@ -1,6 +1,14 @@ -The `LandControl` PQSMod allows very detailed control over the coloration of terrain, creation of ground scatters, minimum terrain height, and much more. +**Internal class name:** `PQSLandControl` -It is both verbose and complex with multiple key parts, so in the interest of ease of use and readability (as well as the sanity of the maintainers), LandControl will be split up into a series of pages. +The `LandControl` PQSMod allows very detailed control over the coloration of terrain, creation of ground scatters, local adjustments in terrain height, and much more. + +It is both verbose and complex with multiple key parts, so in the interest of ease of use and readability (as well as the sanity of the maintainers), LandControl will be split up into a series of pages. All you need to know for now is that LandControl defines a series of regions called LandClasses. These regions are limited in terms of altitude, latitude and longitude, and they define the terrain scatters found therein. + +A notable example of LandControl in action is [Kerbin](https://github.com/Kopernicus/kittopia-dumps/blob/8a4e0737f18ee2b9755e4f7f2451e9de56f2a82f/Configs/Kerbin.cfg#L438); LandControl is wholly responsible for Kerbin's terrain color. As such, Kerbin's use of LandControl will be used as an example in this explanation. + +To illustrate the point made previously: Kerbin uses LandControl to specify the colors **and location** of its desert and polar regions. Because these LandClasses are responsible for the terrain scatters of cacti and pine trees respectively, cacti and pine trees will only appear in the areas affected by the aforementioned LandClasses (or any other LandClass that allows them to spawn). + +LandControl as a PQSMod firstly specifies a series of parameters that govern the **global** behavior of the PQSMod. That is to say, they affect the entire planet. It also specifies an array of named terrain scatter objects, and an array of LandClasses which specify the **local** behavior. These LandClasses may in turn reference elements in the Scatters array by name. Each of these three areas of configuration (global settings, local settings (LandClasses) and terrain scatters) are explained extensively in their own sub-pages: * [LandControl PQSMod]( /Syntax/PQSMods/LandControl/LandControl) * [LandClasses]( /Syntax/PQSMods/LandControl/LandClasses) diff --git a/wwwroot/content/syntax/PQSMods/LandControl/ScatterMaterialType.md b/wwwroot/content/syntax/PQSMods/LandControl/ScatterMaterialType.md index f892330..5f52d64 100644 --- a/wwwroot/content/syntax/PQSMods/LandControl/ScatterMaterialType.md +++ b/wwwroot/content/syntax/PQSMods/LandControl/ScatterMaterialType.md @@ -1,6 +1,30 @@ The contents of the `Material {}` node can very greatly and depend on the value assigned to `materialType` ## Diffuse {#Diffuse} +This shader only renders a color texture. It does not offer a normal map or detail textures. Due to its simplicity, this shader lends itself very well to small opaque detail objects for which per-pixel normal vectors don't contribute too much to the overall appearance. The color parameter is applied multiplicatively to the output of the texture sample. + +### Pseudocode {#Diffuse_Pseudocode} +```csharp +color render_pixel(vector2 texture_coordinate, vector3 surface_normal) +{ + // Load the color set in the Material node. + color output_color = GET_PARAMETER_VALUE("color"); + + // Transform the texture coordinates in accordance with the mainTexScale/Offset parameters. + vector2 main_texture_coordinates = (texture_coordinates * GET_PARAMETER_VALUE("mainTexScale")) + GET_PARAMETER_VALUE("mainTexOffset"); + + // Sample the texture and multiply with the base color from the Material. + output_color *= SAMPLE_TEXTURE("mainTex", main_texture_coordinates); + + // Apply shading. + APPLY_LIGHTING(output_color, surface_normal); + + // Return the shaded, colored pixel. + return output_color; +} +``` + +### Example {#Diffuse_Example} ``` Material { @@ -12,6 +36,37 @@ Material ``` ## BumpedDiffuse {#BumpedDiffuse} +A cousin of the above Diffuse shader that also offers a normal map for per-pixel surface normals. This offers a more detailed appearance. If you do not intend to use a normal map, consider using the above basic Diffuse shader for performance. + +### Pseudocode {#BumpedDiffuse_Pseudocode} +```csharp +color render_pixel(vector2 texture_coordinate, vector3 surface_normal) +{ + // Load the color set in the Material node. + color output_color = GET_PARAMETER_VALUE("color"); + + // Transform the texture coordinates in accordance with the mainTexScale/Offset parameters. + vector2 main_texture_coordinates = (texture_coordinates * GET_PARAMETER_VALUE("mainTexScale")) + GET_PARAMETER_VALUE("mainTexOffset"); + + // Sample the texture and multiply with the base color from the Material. + output_color *= SAMPLE_TEXTURE("mainTex", main_texture_coordinates); + + // Sample the normal map. + vector2 normalmap_coordinates = (texture_coordinates * GET_PARAMETER_VALUE("bumpMapScale")) + GET_PARAMETER_VALUE("bumpMapOffset"); + vector3 normalmap = SAMPLE_NORMALMAP("bumpMap", normalmap_coordinates); + + // Adjust the surface normal using the normal map. + surface_normal = ADJUST_SURFACE_NORMAL(surface_normal, normalmap); + + // Apply shading, now taking the normal map into account. + APPLY_LIGHTING(output_color, surface_normal); + + // Return the shaded, colored pixel. + return output_color; +} +``` + +### Example {#BumpedDiffuse_Example} ``` Material { @@ -26,6 +81,34 @@ Material ``` ## DiffuseDetail {DiffuseDetail} +Another cousin of the base Diffuse shader. This variant uses a secondary color texture that is applied multiplicatively to the color output. This allows for some further detail using a secondary, tilable detail texture without having to increase the main texture resolution. + +### Pseudocode {#DiffuseDetail_Pseudocode} +```csharp +color render_pixel(vector2 texture_coordinate, vector3 surface_normal) +{ + // Load the color set in the Material node. + color output_color = GET_PARAMETER_VALUE("color"); + + // Transform the texture coordinates in accordance with the mainTexScale/Offset parameters. + vector2 main_texture_coordinates = (texture_coordinates * GET_PARAMETER_VALUE("mainTexScale")) + GET_PARAMETER_VALUE("mainTexOffset"); + + // Sample the texture and multiply with the base color from the Material. + output_color *= SAMPLE_TEXTURE("mainTex", main_texture_coordinates); + + // Sample the detail map and multiply it with the respective color channels. + vector2 detailmap_coordinates = (texture_coordinates * GET_PARAMETER_VALUE("detailScale")) + GET_PARAMETER_VALUE("detailOffset"); + output_color *= SAMPLE_TEXTURE("detail", detailmap_coordinates); + + // Apply shading. + APPLY_LIGHTING(output_color, surface_normal); + + // Return the shaded, colored pixel. + return output_color; +} +``` + +### Example {#DiffuseDetail_Example} ``` Material { @@ -52,6 +135,43 @@ Material ``` ## CutoutDiffuse {#CutoutDiffuse} +Another variant of the base diffuse shader. This shader uses a feature called 'opacity clip' or 'alpha clip'. Put simply, the shader discards the pixel being rendered if the alpha channel of the pixel is below some threshold cutoff value. This does not allow for smooth, overlay transparency like with glass. It will be a binary transparency. + +This does still let Unity render the scatter as an opaque object instead of a transparent one, and this results in better performance* and accurate depth sorting. An example use case is foliage, which can use a quad mesh and a cutout shader to achieve a smooth, detailed leaf shape without requiring a lot of mesh vertices. + +_Asterisk: this is because Unity renders opaque objects front-to-back. Nearby objects have a relatively low probability of being occluded by other objects, so rendering in this order means that Unity can skip per-pixel rendering for a lot of objects in the scene, because it can easily verify that the pixel it is looking to render is behind a pixel that it has already drawn. This is called the Z-test, and it avoids having to perform potentially costly shading and lighting calculations. If we instead render back-to-front then we will be needlessly evaluating the pixel shader: we'll render the entire surface of object A, only to draw over the output with the surface of object B, only to render over that with object C, and so on. Doing a front-to-back render greatly reduces the issue of overdraw, IE where we waste work by drawing pixels that we end up replacing._ + +_For transparent objects, we have no choice but to do a back-to-front render since there is no predicting which pixels will be fully opaque, and which pixels will blend with what was already drawn. In this case we have to fo a back-to-front rendering order because then we would be rendering objects in the order that a ray of light would encounter them on its trajectory toward the viewer. But if we do not need such precise blending and can get away with binary opacity, then a cutout shader lets us still use the opaque render queue, and lets us avoid a lot of overdraw and thus a lot of pointless shader evaluations._ + +### Pseudocode {#CutoutDiffuse_Pseudocode} +```csharp +color render_pixel(vector2 texture_coordinate, vector3 surface_normal) +{ + // Load the color set in the Material node. + color output_color = GET_PARAMETER_VALUE("color"); + + // Transform the texture coordinates in accordance with the mainTexScale/Offset parameters. + vector2 main_texture_coordinates = (texture_coordinates * GET_PARAMETER_VALUE("mainTexScale")) + GET_PARAMETER_VALUE("mainTexOffset"); + + // Sample the texture and multiply with the base color from the Material. + output_color *= SAMPLE_TEXTURE("mainTex", main_texture_coordinates); + + if (color.alpha < GET_PARAMETER_VALUE("cutoff")) + { + // The pixel is not rendered onto the screen, into shadow maps or into the depth buffer. + // It will be as though the mesh does not exist at this point in space. + DISCARD_PIXEL(); + } + + // Apply shading. + APPLY_LIGHTING(output_color, surface_normal); + + // Return the shaded, colored pixel. + return output_color; +} +``` + +### Example {#CutoutDiffuse_Example} ``` Material { @@ -80,50 +200,172 @@ Material ``` ## Standard {#Standard} + +This is the slowest and most accurate shader available, because it can produce a physically accurate shading output. Its full list of features are: +- Per-pixel surface normals. +- Opacity/Alpha clipping (requires `mode = Cutout`) +- True transparency with blending control. +- Color and normal detail textures, with an optional `detailMask` parameter to control their prominence on a per-pixel level. +- The ability to choose between texture coordinate channels 0 (default) and 1 (typically used for baked lighting). +- Per-pixel ambient occlusion through a texture. +- Parallax mapping. This offsets texture samples based on a heightmap to create the illusion of per-pixel surface displacement. +- Precise (optionally per-pixel) control over the surface material properties (smoothness and metallicity). +- Emission, to create the illusion of the surface giving off light. + +Given the complexity of this shader and the vagueness of the parameters to the uninitiated, we'll discuss the 'new' features in greater detail. +Note: you may also wish to consult the [Unity documentation]( https://docs.unity3d.com/2019.4/Documentation/Manual/shader-StandardShader.html) on this shader. + +### Parallax Mapping {#Standard_ParallaxMapping} + +See also: the [Unity Documentation]( https://docs.unity3d.com/2019.4/Documentation/Manual/StandardShaderMaterialParameterHeightMap.html) on parallax mapping. + +Parallax Mapping gives the illusion of per pixel depth and displacement without generating additional geometry. It uses a simple screen-space ray-trace and a heightmap to achieve this. In essence, the logic behind it is this: we are rendering a flat surface (namely the surface of a mesh triangle), but what if, though a heightmap, we had a measure of the surface's actual shape? Through the heightmap we can tell how 'inset' the surface ought to be. So through a simple per-pixel ray-trace, we can try to step forward 'into' the surface, until we intersect where the 'true' surface would have been, using the heightmap as a guide. If we adjust the texture coordinates accordingly then this creates an illusion of surface detail that only falls apart at very shallow viewing angles. + +The relevant parameters are: +| Property | Format | Description | +|---|---|---| +| parallax | Decimal | If white pixels in the heightmap are at the surface, how 'deep' would a black pixel be inset into the surface? That's what you define with this parameter. | +| parallaxMap | Texture | The heightmap to use for parallax mapping. If not set, then this feature is not used. | +| parallaxMapScale | Vector2 | Per-coordinate scaling of the texture coordinates when sampling the parallax map. | +| parallaxMapOffset | Vector2 | Per-coordinate offset of the texture coordinates when sampling the parallax map. | + + +### Pseudocode {#Standard_Pseudocode} +```csharp +color render_pixel(vector2 uv_channel_0, vector2 uv_channel_1, vector3 surface_normal, color current_pixel_value) +{ + // Determine the texture coordinate to use. + vector2 texture_coordinates; + if (GET_PARAMETER_VALUE("UVSec") == 1) + texture_coordinates = uv_channel_1; + else + texture_coordinates = uv_channel_0; + + // Sample the heightmap. + vector2 heightmap_coordinates = (texture_coordinates * GET_PARAMETER_VALUE("parallaxMapScale")) + GET_PARAMETER_VALUE("parallaxMapOffset"); + value height = SAMPLE_TEXTURE("parallaxMap", heightmap_coordinates).red_channel; + // Shift the texture coordinates using parallax mapping. + texture_coordinates = APPLY_PARALLAX_MAPPING(texture_coordinates, height); + + + // Load the color set in the Material node. + color output_color = GET_PARAMETER_VALUE("color"); + + // Transform the texture coordinates in accordance with the mainTexScale/Offset parameters. + vector2 main_texture_coordinates = (texture_coordinates * GET_PARAMETER_VALUE("mainTexScale")) + GET_PARAMETER_VALUE("mainTexOffset"); + + // Sample the texture and multiply with the base color from the Material. + output_color *= SAMPLE_TEXTURE("mainTex", main_texture_coordinates); + + BlendMode mode = GET_PARAMETER_VALUE("mode"); + if (mode == Cutout) + { + if (color.alpha < GET_PARAMETER_VALUE("cutoff")) + { + // The pixel is not rendered onto the screen, into shadow maps or into the depth buffer. + // It will be as though the mesh does not exist at this point in space. + DISCARD_PIXEL(); + } + } + + // Apply shading. + APPLY_LIGHTING(output_color, surface_normal); + + // Return the shaded, colored pixel. + return output_color; +} ``` + Material { color = Color, default is 1,1,1,1 + + // Main texture. + // If smoothnessTextureChannel = AlbedoAlpha then the alpha channel of this texture is sampled for per-pixel smoothness. mainTex = Texture, default is white mainTexScale = vector2 mainTexOffset = vector2 + + // Opacity clip cutoff = Float, default is 0.5, Alpha Cutoff + + // Smoothness source control + smoothnessTextureChannel = TextureChannel, default is MetallicAlpha (0), can be either MetallicAlpha (0) or AlbedoAlpha (1) + + // Smoothness scale glossiness = Float, default is 0.5, smoothness + + // Standalone smoothness control (when using AlbedoAlpha?) glossMapScale = Float, default is 1 - smoothnessTextureChannel = TextureChannel, default is 0 + + // Metallicity control metallic = Float, default is 0 + + // Per-pixel metallicity & smoothness texture. If smoothnessTextureChannel = MetallicAlpha then the alpha channel of this texture is sampled for per-pixel smoothness. + metallicGlossMap = Texture, default is "white" metallicGlossMapScale = Vector2 metallicGlossMapOffset = Vector2 + + // If True, lights shining upon this surface create a highlight where the surface normal causes most light to reflect toward the camera. + // Imagine the glare of the sun reflecting off a car. specularHighlights = Boolean, default is true + + // If true, then smooth surfaces will use reflection probe data to reflect the environment light. + // If false, you will not see the environment reflected in smooth surfaces. glossyReflections = Boolean, default is true + + // Main normal map settings. bumpScale = Float, default is 1 bumpMap = Texture, default is "bump" bumpMapScale = vector2 bumpMapOffset = vector2 + + // Parallax mapping settings. parallax = Float, default is 0.02, height scale parallaxMap = Texture, default is black + parallaxMapScale = Vector2 parallaxMapOffset = Vector2 + + // Ambient occlusion settings. occlusionStrength = Float, default is 1 occlusionMap = Texture, default is white occlusionMapScale = Vector2 occlusionMapOffset = Vector2 + + // Emissivity settings. emissionColor = Color, default is 0,0,0,1 emissionMap = Texture, default is white emissionMapScale = Vector2 emissionMapOffset = Vector2 + + // Detail mask: a black/white texture providing per-pixel prominence of detail textures. detailMask = Texture, default is white detailMaskOffset = Vector2 detailMaskScale = Vector2 + + // Detail color map that gets blended with the color data. detailAlbedoMap = Texture, default is grey detailAlbedoMapOffset = Vector2 detailAlbedoMapScale = Vector2 + + // Detail normal map that gets applied on top of the existing bump map to add additional details. detailNormalMap = Texture, default is bump detailNormalMapScale = Vector2 detailNormalMapOffset = Vector2 - UVSec = UvSet, default is 0 + + // Control over which UV channel is used. + UVSec = UvSet, either 0 or 1, default is 0 + + // Which blending mode should be used. Options are Opaque, Cutout, Fade and Transparent with numeric values 0, 1, 2 and 3 respectively. mode = BlendMode, default is 0 + + // The source and destination blending mode. + // Full articles by Unity: https://docs.unity3d.com/2019.4/Documentation/ScriptReference/Rendering.BlendMode.html + // https://docs.unity3d.com/2019.4/Documentation/Manual/SL-Blend.html srcBlend = Float, default is 1 dstBlend = Float, default is 0 + + // Whether or not the pixel shader should store its distance from the camera in the depth buffer. 0 = no, 1 = yes. ZWrite = Float, default is 0 } ``` diff --git a/wwwroot/content/syntax/PQSMods/LandControl/Scatters.md b/wwwroot/content/syntax/PQSMods/LandControl/Scatters.md index d5a8066..6ead7f3 100644 --- a/wwwroot/content/syntax/PQSMods/LandControl/Scatters.md +++ b/wwwroot/content/syntax/PQSMods/LandControl/Scatters.md @@ -1,4 +1,4 @@ -The `Scatters` node in the LandControl PQSMod defines the scatters to be used within the [LandClasses]( /Syntax/PQSMods/LandControl/LandClasses) provided by LandControl. Scatters are 3D meshed objects that are generated on the surface and can have various configurable features added via the `Components` subnode. +The `Scatters` node in the LandControl PQSMod defines one or more detail meshes that may be scattered across the planet's terrain as little props. The [LandClasses]( /Syntax/PQSMods/LandControl/LandClasses) provided by LandControl determines which scatters may spawn where. They can have various configurable features added via the `Components` subnode. ## Subnodes {#Subnodes} * [Material { }]( /Syntax/PQSMods/LandControl/ScatterMaterial) @@ -43,38 +43,81 @@ LandControl } ``` +## Parameters {#Parameters} +In keeping with the format of other LandControl pages, we shall divide the parameters into sections and discuss them one at a time. +1. **Appearance Control**: these parameters control the appearance of the terrain scatter, regardless of _where_ they appear. +2. **Distribution Control**: these parameters control the distribution (IE placement) of terrain scatters. +3. **Caching Control**: these parameters control the behavior of the caching strategy that KSP uses to try and increase performance. +4. **Kopernicus Parameters**: these are parameters that are not in the game by default, but which were added by Kopernicus as part of the **ModularScatter** extension. + +## Appearance Control {#Appearance_Control} + +These are all the settings that control the appearance of terrain scatters. Most of these parameters are self-evident but the material is more complex. Just know that you can either target a built-in material through the `materialType` property by setting it to the name of the material you want, or you can set the `material` property to target a given built-in shader. The number of options does not change. For the sake of simplicity, using `materialType` is advisable. Avoid using both simultaneously; only use one of the two. + | Property | Format | Description | |---|---|---| -| name | Text | The name of the scatter. | -| allowedBiomes | Text | A comma delimitted string of permitted scatter biome names. No spaces between entries. If this list is not present, all biomes are spawned in. | -| lethalRadius | Decimal | The closest a kerbal can get to this scatter without being killed, in meters. Set to 0 (the default) to disable. | -| lethalRadiusMsg | Text | A message to be displayed in a dialog box when a kerbal is killed by `lethalRadius`. Leave empty to disable. | -| lethalRadiusWarnMsg | Text | A message to be displayed in a dialog box when a kerbal comes within 2x `lethalRadius` to alert the player. Leave empty to disable. | -| seed | Integer | The random seed for scatter distribution. | | materialType | [ScatterMaterialType]( /Syntax/PQSMods/LandControl/ScatterMaterialType) | The type of the material of the scatter. Valid options can be found on the [ScatterMaterialType]( /Syntax/PQSMods/LandControl/ScatterMaterialType) page. | | material | BUILTIN | Stock material to use instead of specifying a materialType and Material { }. Avoid using this! Will not work in conjunction with the materialType and Material { }. | -| mesh | File Path | The path to an .obj file that contains the scatter's mesh. | -| Meshes | List of File Paths | A list of meshes that will be picked randomly. Inside this node, there can be keys named anything, and the value should be the file path to the .obj file. | +| mesh | File Path | The path to an .obj file that contains the scatter's mesh. This can also be a built-in mesh. | | castShadows | Boolean | Whether the scatter should cast shadows. | | receiveShadows | Boolean | Whether the scatter should receive shadows - i.e., have shadows casted upon it. | -| densityFactor | Float | A [0,1] base factor applied to `maxScatter`. Usually you want this set to 1 and just change `maxScatter`. | -| maxCache | Integer | Maximum amount of active scatter quads. Leaving this to the default value (512) should be always fine. | -| maxCacheDelta | Integer | How many quads are added to the cache when it isn't large enough to hold all active scatter quads. `maxCache` must be a multiple of this value. Default value (64) should be fine. | -| maxLevelOffset | Integer | The max offset from the PQS level? (the ones controlled by `minLevel` and `maxLevel`) | -| maxScale | Float | The scatter model(s) will be scaled by a random multipler choosen between `minScale` and `maxScale`. | -| minScale | Float | The scatter model(s) will be scaled by a random multipler choosen between `minScale` and `maxScale`. | -| maxScatter | Integer | The base amount of scatter objects per quad. Actual amount depends on `densityFactor`, the `density` defined in the `LandClasses` node and `spawnChance` if `useBetterDensity` is true. | -| maxSpeed | Float | Scatter quads won't be created/rendered if the active vessel speed (in m/s) is higher than this value. Due to a stock bug, this is unreliable and quads might still appear anyway. | -| verticalOffset | Float | Vertical offset from the ground in meters for scatter objects placement. | -| instancing | Boolean | Whether to instance the material, presumably to create better performance? | -| useBetterDensity | Boolean | Set this to true to enable randomization of the amount of scatter objects per quad. | -| spawnChance | Float | Requires `useBetterDensity` to be true. [0, 1] probability of each scatter object spawning. | -| ignoreDensityGameSetting | Boolean | If set to true, the KSP main menu settings scatter density % will be ignored. | +| maxScale | Decimal | The scatter model(s) will be scaled by a random multipler choosen between `minScale` and `maxScale`. | +| minScale | Decimal | The scatter model(s) will be scaled by a random multipler choosen between `minScale` and `maxScale`. | +| verticalOffset | Decimal | Vertical offset added to placed scatters. Use this to move them up or down along the radial. | -## Scatter density {#Density} +## Distribution Control {#Distribution_Control} + +These parameters offer some control over how and where terrain scatters are distributed. To gain a full understanding, it is necessary to discuss the apparent quirks of terrain scatters. + +### Scatter density {#Scatter_Density} The amount of individual scatters per quad is determined as follows :\ `baseAmount = maxScatter * densityFactor * (density defined in the LandClass)` Then if `useBetterDensity` is enabled, the average amount will be :\ `averageAmount = baseAmount * spawnChance` + +Furthermore, `maxScatter` seems to act like some kind of limiter to terrain scatter density. The exact behavior is unclear. The main takeaway is that increasing it, increases the number of scatters. + +### Scatter Filtering {#scatter_Filtering} +There are two parameters that actually control where and when terrain scatters may spawn. The first is `maxSpeed`. This parameter is rather buggy but the intended behavior seems to be that it prevents a scatter from appearing unless the speed (in m/s) of the craft or camera is lower than the given value. + +The other parameter is `maxLevelOffset` and this is a bit more complex. Recall that planets in KSP are just patchworks of 2D grids that have their vertices displaced to the surface of a sphere. The closer a tile is to the camera, the more detailed it is. This is an important optimization because it greatly reduces the render cost of distant tiles without significant loss of quality. After all, distant tiles typically take up less screen space and as such their features may be less pronounced. As an added benefit, the GPU strongly dislikes triangles that are approaching pixel scale. Keeping these triangles larger therefore has a secondary benefit to the rendering performance. + +Similarly, there isn't much need for terrain scatters on tiles that are far away from the camera. The render cost is typically not worth the insignificant change in visual quality. To this end, LandControl only places terrain scatters on planet tiles that have a high enough detail level. Specifically, LandControl looks for the **smallest** value of `maxLevelOffset` and adds it to the `maxLevel` parameter of the base PQS settings. This, then, forms the **minimum level of subdivision** for scatters to spawn. + +Leaving this at 0 means that scatters only appear on the highest level of subdivision, meaning the tiles that are closest to the camera. Increasing it may cause a terrain scatter to never be applicable for display, whilst decreasing it below zero may cause scatters to appear from a greater distance but at a cost to performance. Remember that LandControl will look for the smallest value assigned to `maxLevelOffset` across all terrain scatters that are defined in the mod. Setting the value lower will always result in a bit of additional processing overhead for planet tiles that suddenly become applicable for terrain scatters. + +| Property | Format | Description | +|---|---|---| +| name | Text | The name of the scatter. This is the name you refer to from LandClasses to allow this scatter to spawn within that LandClass. | +| seed | Integer | The random seed for scatter distribution. | +| densityFactor | Decimal | A [0,1] base factor applied to `maxScatter`. Usually you want this set to 1 and just change `maxScatter`. | +| maxLevelOffset | Integer | By default, LandControl will only place scatters on tiles that are at the maximum level(s) of subdivision. This value is an offset to that level of subdivision: -1 means that the scatters should start appearing exactly one subdivision level sooner. | +| maxScatter | Integer | The base amount of scatter objects per quad. Actual amount depends on `densityFactor`, the `density` defined in the `LandClasses` node and `spawnChance` if `useBetterDensity` is true. | +| maxSpeed | Decimal | Scatter quads won't be created/rendered if the active vessel speed (in m/s) is higher than this value. Due to a stock bug, this is unreliable and quads might still appear anyway. | + +## Caching Control {#Caching_Control} + +By default, KSP tries to optimize the terrain scatters. It does so by trying to cache and merge terrain scatters. These parameters let you control this caching behavior, although the default settings should work fine in most cases. + +| Property | Format | Description | +|---|---|---| +| maxCache | Integer | Maximum amount of active scatter quads. Leaving this to the default value (512) should be always fine. | +| maxCacheDelta | Integer | How many quads are added to the cache when it isn't large enough to hold all active scatter quads. `maxCache` must be a multiple of this value. Default value (64) should be fine. | + +## Kopernicus Parameters {#Kopernicus_Parameters} +The built-in terrain scatters are rather limited in their scope. As such, Kopernicus replaces the built-in terrain scatters with an extended framework that offers various new features. Some of these are built into these new terrain scatters without having to use components. + +| Property | Format | Description | +|---|---|---| +| allowedBiomes | Text | A comma delimited string of permitted scatter biome names. No spaces between entries. If set, then this terrain scatter can only spawn on terrain that belongs to the given biome(s). | +| lethalRadius | Decimal | The closest a Kerbal on EVA can get to this scatter without being killed, in meters. Set to 0 (the default) to disable. | +| lethalRadiusMsg | Text | A message to be displayed in a dialog box when a Kerbal is killed by `lethalRadius`. Leave empty to disable. | +| lethalRadiusWarnMsg | Text | A message to be displayed in a dialog box when a Kerbal comes within 2x `lethalRadius` to alert the player. Leave empty to disable. | +| Meshes | List of File Paths | Optionally, a list of meshes from which Kopernicus will randomly pick. Inside this node, there can be keys named anything, and the value should be the file path to the .obj file. This serves as a replacement for the regular `mesh` property, in case you have multiple variants of the same terrain scatter. Note that these will use the same material. | +| useBetterDensity | Boolean | Set this to true to enable randomization of the amount of scatter objects per quad. | +| spawnChance | Decimal | Requires `useBetterDensity` to be true. [0, 1] probability of each scatter object spawning. | +| ignoreDensityGameSetting | Boolean | If set to true, the KSP main menu settings scatter density % will be ignored. | +| instancing | Boolean | Enables or disables object instancing support on the scatter's generated material. This lets Unity render identical meshes that use identical material instances within a single draw call. | +| rotation | 2 Decimals | To add some visual variety, terrain scatters are randomly rotated along their local up axis. This parameter lets you control the minimum and maximum angle (in degrees), if you so wish. By default, full 360 degree rotation is set. | diff --git a/wwwroot/content/syntax/Rings.md b/wwwroot/content/syntax/Rings.md index 5569cd1..2445dba 100644 --- a/wwwroot/content/syntax/Rings.md +++ b/wwwroot/content/syntax/Rings.md @@ -1,6 +1,190 @@ Each `Body { }` may have a node called `Rings { }`, a wrapper node containing one or more `Ring { }` nodes, each of which defines a planetary ring. These may be flat discs as with Saturn, loops of ribbon as with the Ringworld, or voluminous cylinders. -Distance units for rings are milliradii, thousandths of the radius of the parent body. So 1000 is 1 radius, 2000 is 2 radii, etc. +Distance units for rings are milliradii, thousandths of the radius of the parent body. So 1000 is 1 radius, 2000 is 2 radii, etc. This way, rings automatically scale with the planet. + +## Properties {#Properties} +For the sake of clarity, we will discuss the parameters of the ring in several categories. + +### Shape Properties {#Shape_Properties} +The parameters `innerRadius` and `outerRadius` are self-explanatory, but the `InnerRadiusMultiplier` and `OuterRadiusMultiplier` less so. These FloatCurves exist **for non-circular rings**. They essentially map angle (degrees, x coordinate) to radius multiplier (y coordinate). This gives precise and total control over the shape of the ring. + +|Property|Format|Description| +|--------|------|-----------| +|innerRadius|Decimal|The distance from center of parent to inner edge of ring in milliradii.| +|outerRadius|Decimal|The distance from center of parent to outer edge of ring in milliradii.| +|InnerRadiusMultiplier|FloatCurve|A curve that defines a multiplier for the inner radius using an angle. The first value is an angle in degrees, while the second is the multiplier. Allows for the deformation of rings.| +|OuterRadiusMultiplier|FloatCurve|Similar to `InnerRadiusMultiplier`, but for the outer radius rather than the inner radius.| +|thickness|Decimal|The distance between top and bottom faces of ring in milliradii.| +|steps|Integer|The amount of vertices around the ring.| + +### Placement Properties {#Placement_Properties} + +|Property|Format|Description| +|--------|------|-----------| +|angle|Decimal|The angle in degrees between the plane of the ring and the equatorial plane of the parent planet.| +|longitudeOfAscendingNode|Decimal|Angle in degrees between the absolute reference direction and the ascending node. Works just like the corresponding property on celestial bodies. Only effective if `lockRotation` is true.| +|lockRotation|Boolean|Whether to lock the rotation of the ring. If false, the ring's LAN rotates with the parent body (unnatural if the `angle` is not 0).| +|rotationPeriod|Decimal|The number of seconds for the ring to complete one rotation. If zero, it will default to the parent body's `rotationPeriod`. Only noticeable if `tiles` is not 0.| + +### Appearance Properties {#Appearance_Properties} + +These properties define how the ring is shaded. + +|Property|Format|Description| +|--------|------|-----------| +|texture|File Path|The path to the ring texture.| +|color|Color|A tint applied to the ring.| +|unlit|Boolean|Whether to apply an Unlit/Transparent shader instead of a Transparent/Diffuse shader.| +|useNewShader|Boolean|Whether to use the new custom ring shader that includes a planet shadow instead of the built-in Unity shaders.| +|penumbraMultiplier|Decimal|A penumbra multiplier to the NewShader. Makes planet shadow softer (values larger than one) or less soft (smaller than one). Softness still depends on distance from sun, distance from planet and radius of sun and planet.| +|tiles|Integer|Number of times the texture should be tiled around the cylinder. If zero, use the old behavior of sampling a thin diagonal strip from (0,0) to (1,1). Look at the example above for more info.| +|innerShadeTexture|File Path|The path to the texture whose opaque pixels cast shadows on the ring's inner surface.| +|innerShadeTiles|Integer|The `innerShadeTexture` repeats this many times over the inner surface.| +|innerShadeRotationPeriod|Decimal|The number of seconds the `innerShadeTexture` takes to complete one rotation.| + +### Fadeout Properties {#Fadeout_Properties} + +_Note: the parameters in this sub-section are optional and apply only if_ `useNewShader = True`. + +Rings typically occupy a sizable chunk of the host body's sphere of influence. As such, it is not unusual for vessels to pass through the rings. Unfortunately this by default introduces an immersion breaking experience when the screen the ring's mesh. To solve this, the custom ring shader (used when `useNewShader = True`) includes a fade-out that is applied if the camera is close to the rings. + +By default, the rings start to fade out smoothly from 100 Unity units and closer, becoming fully transparent at or under 20 Unity units from the camera. + +These parameters offer explicit control over this fade-out mechanic. + +|Property|Format|Description| +|--------|------|-----------| +|fadeoutStartDistance|Decimal|The distance to the camera, in Unity units, whereat the rings should start fading out. Default is 100.| +|fadeoutStoptDistance|Decimal|The distance to the camera, in Unity units, whereat the rings should be fully faded out. Default is 20.| +|fadeoutMinAlpha|Decimal|The minimum opacity of the rings. Zero by default. Can be set to 1 to disable fadeout entirely.| + +### Detail Properties {#Detail_Properties} + +_Note: the parameters in this sub-section are optional and apply only if_ `useNewShader = True`. + +By default, a single texture is used for a ring that spans thousands of miles or km. Using a single texture to obtain fine details would require a huge amount of pixels and this is not acceptable for obvious reasons. To solve this, the custom ring shader allows for two detail passes. + +All of the detail parameters must be defined under a Detail node which itself is nested in the Ring node. + +The Detail node itseld contains the following parameters. + +|Property|Format|Description| +|--------|------|-----------| +|detailRegionsTexture|File Path|A path to a secondary ring texture wherein the RGBA color channels regulate the prominence of the respective channels of the two detail textures at the given mask pixel's position in the ring. Use this to control which detail textures are shown at which position.| +|detailRegionsMask|4D Vector|Multiplied with the sample of the regions texture. Use this to add or reduce the strength of color channels. Creative users may even use this feature to share textures between rings. + +### Detail Passes {#Detail_Passes} +These properties exist within two uniquely named subnodes of `Detail`, namely `Coarse` and `Fine`. These names are arbitrary, the only true difference is that the `Coarse` pass is applied first. + +Each of the two detail passes references a texture. Normally you would expect a texture to store color (and optionally opacity) information. However, we can also use textures to store other kinds of information, by not actually interpreting the texture sample as color data. An example that is used in Unity is to pack the per-pixel metallicity and smoothness values into a single texture, or for opaque non-metallic objects, to store the smoothness in the opacity channel of the color texture. + +The big benefit of this is that shaders may have a limit to the number of textures they can sample. Instead of referencing and sampling several grayscale textures, it is more efficient to reduce the number of textures by packing three or four grayscale textures into a single false color texture. In the case of the ring shader, it is expected that each of the four color channels of the detail mask describes a different grayscale noise texture. This way, up to four different detail patterns can be displayed per-detail-pass without any additional texture samples being required. + +Additionally, it may allow a single texture to be shared between rings or detail passes. Reducing the number of total and/or loaded textures improves the memory usage of KSP. + +To further empower the re-use of textures, each detail pass lets one adjust the minimum and maximum opacity multiplier of each of the four noise texture channels. This is done through the `alphaMin` and `alphaMax` parameters. This can be used to adjust the blending strength on a per-texture-channel basis. Additionally, the `strength` parameter acts as an altogether multiplier for the change in opacity made by this detail pass. That is to say, the change in opacity as a consequence of the detail pass is scaled by `strength`. + +Ideally the detail effect should start fading in on close proximity. It may be desirable to also allow the detail pass to fade out on closer proximity, to retain total control. This is what the parameters `fadeInStart`, `fadeInEnd`, `fadeOutStart` and `fadeOutEnd` are for. Some special use cases: +- If you do not want a fade-in, set `fadeInEnd = -1` and `fadeInStart = -2`. If doing this, the detail will only fade-out on close proximity. +- If you do not want a fade-out, set `fadeOutStart = -1` and `fadeOutEnd = -2`. If doing this, the detail will only fade-in as the camera approaches, but not fade out on close proximity. +- If using both of the above, there is no fading and the `strength` parameter has total control over the prominence of the detail pass. + +|Property|Format|Description| +|--------|------|-----------| +|detailMask|4D Vector|A per-detail-pass multiplier to the prominence of each of the color channels of the texture. Use this to adjust the intensity of each of the detail color channels.| +|texture|File Path|The detail texture to use at this position. This should be a channel packed detail texture, IE this is not a color texture, but instead each of the four color channels encodes a separate noise texture.| +|alphaMin|4D Vector|The minimum ring opacity multiplier for each detail texture channel.| +|alphaMax|4D Vector|The maximum ring opacity multiplier for each detail texture channel.| +|tiling|2D Vector|The tiling of the detail texture along the ring.| +|strength|Decimal|The strength of this detail pass.| +|fadeInStart|Decimal|The distance from the camera at which this detail pass should start contributing.| +|fadeInEnd|Decimal|The distance from the camera at which this detail pass should be at full strength.| +|fadeOutStart|Decimal|The distance from the camera at which this detail pass should start fading out again.| +|fadeOutEnd|Decimal|The distance from the camera at which this detail pass should no longer be visible.| + +### How Detail Works {#How_Detail_Works} +This section aims to clarify the exact operations performed by the shader, in plain English. + +1. Generate coordinates for the detail textures. (This happens on a per-vertex basis.) +2. Sample `detailRegionsTexture` using the same texture coordinates as the main texture. Multiply the result by `detailRegionsMask`. Let's call this the 'global detail mask'. +3. Sample both detail textures using the generated coordinates and the per-detail-pass tiling parameters. This results in two 4D values. +4. Use these 4D values to lerp between the per-detail-pass `alphaMin` and `alphaMax` parameters. +5. Take the dot product of these values and the global detail mask, multiplied by the per-detail-pass `detailMask` parameters. This is the detail noise value for this channel. +6. Calculate the prominence of each detail pass by interpolating between the `fadeIn` and `fadeOut` parameters. A smoothstep interpolation is used. +7. Multiply the per-pass prominence with the `strength` parameter of that pass. +8. Multiply the opacity of the rings with the detail noise value of the `Coarse` pass. Blend this result with the original opacity value using the prominence of the `Coarse` pass. +9. Multiply the (new) opacity of the rings with the detail noise value of the `Fine` pass. Blend this result with the original opacity value using the prominence of the `Fine` pass. + +Or in C# code: + +```csharp +struct DetailFeature +{ + Texture detailRegionsTexture; + Vector_4D detailRegionsMask; + DetailPass coarse; + DetailPass fine; + + // Apply the detail feature to the ring opacity. + float Apply(float ringOpacity, float cameraDistance, Vector_2D ringTextureCoords, Vector_2D ringDetailCoords) + { + // Get the global detail channel mask. + Vector_4D globalDetailMask = SampleTexture(detailRegionsTexture, ringTextureCoords) + * detailRegionsMask; + // Apply both detail channels in sequence. + ringOpacity = coarse.Apply(ringOpacity, cameraDistance, ringDetailCoords, globalDetailMask); + ringOpacity = fine.Apply(ringOpacity, cameraDistance, ringDetailCoords, globalDetailMask); + + return ringOpacity; + } +} + +struct DetailPass +{ + Vector_4D detailMask; + Texture texture; + Vector_4D alphaMin; + Vector_4D alphaMax; + Vector_2D tiling; + float strength; + float fadeInStart; + float fadeInEnd; + float fadeOutStart; + float fadeOutEnd; + + // Apply this detail pass to the ring opacity. + float Apply(float ringOpacity, float cameraDistance, Vector_2D ringDetailCoords, Vector_4D globalDetailMask) + { + // First we calculate the prominence of the detail pass. + // That is to say, how intense should the effect be here. + float fade_in = smoothstep(fadeInStart, fadeInEnd, cameraDistance); + float fade_out = smoothstep(fadeOutEnd, fadeOutStart, cameraDistance); + float prominence = fade_in * fade_out * strength; + + // Get the detail value. + Vector_4D detailChannels = SampleTexture(texture, ringDetailCoords * tiling); + // Linearly interpolate between the per-channel min and max alpha multiplier. + detailChannels = alphaMin + (detailChannels * (alphaMax - alphaMin)); + + // Get the channel filter. + Vector_4D channelFilter = globalDetailMask * detailMask; + + // Compute the final detail noise value by adding the detail channels together, using the channel filter as a per-channel prominence value. + float detailValue = (detailChannels.x * channelFilter.x) + + (detailChannels.y * channelFilter.y) + + (detailChannels.z * channelFilter.z) + + (detailChannels.w * channelFilter.w); + + // What would the effect of applying the detail be? + float appliedOpacity = ringOpacity * detailValue; + + // Scale the intensity of the effect by the prominence. + float changeInOpacity = appliedOpacity - ringOpacity; + return ringOpacity + (prominence * changeInOpacity); + } +} +``` +Notice: the above shader code is not optimized and is written to enhance readability. In the actual shader the effect is implemented so as to take advantage of SIMD/vectorized arithmetic. ## Example {#Example} ``` @@ -44,6 +228,44 @@ Rings lockRotation = true longitudeOfAscendingNode = 30 rotationPeriod = 600 + + // Ring proximity fadeout parameters + fadeoutStartDistance = 200 + fadeoutStopDistance = 20 + fadeoutMinAlpha = 0 + + // Ring detail pass. Omit this node to disable. + Detail + { + detailRegionsMask = 1, 1, 1, 1 + detailRegionsTexture = ... + Coarse + { + detailMask = 1, 1, 1, 1 + texture = ... + alphaMin = 0, 0, 0, 0 + alphaMax = 1, 1, 1, 1 + tiling = 30, 400 + strength = 0.3 + fadeInStart = 8000 + fadeInEnd = 3000 + fadeOutStart = 1000 + fadeOutEnd = 500 + } + Fine + { + detailMask = 1, 1, 1, 1 + texture = ... + alphaMin = 0, 0, 0, 0 + alphaMax = 1, 1, 1, 1 + tiling = 60, 800 + strength = 0.7 + fadeInStart = 1200 + fadeInEnd = 450 + fadeOutStart = 400 + fadeOutEnd = 40 + } + } } } ``` From ae4c5045154f72154a504bef30e675391a40b639 Mon Sep 17 00:00:00 2001 From: TWG Date: Wed, 31 Jul 2024 17:16:02 +0200 Subject: [PATCH 2/3] Add more PQSMods + community mod Also add the internal mod name because this is used by the Template node. --- .../PQSMods/AerialPerspectiveMaterial.md | 34 +++++ .../content/syntax/PQSMods/AltitudeAlpha.md | 28 ++++ .../VertexMitchellNetravaliHeightMap.md | 136 ++++++++++++++++++ .../content/syntax/PQSMods/HeightColorMap.md | 13 +- .../content/syntax/PQSMods/HeightColorMap2.md | 8 +- .../syntax/PQSMods/LandControl/LandControl.md | 2 + wwwroot/content/syntax/PQSMods/PQSMods.md | 33 +++-- .../content/syntax/PQSMods/VertexColorMap.md | 3 + .../syntax/PQSMods/VertexColorMapBlend.md | 3 + .../content/syntax/PQSMods/VertexHeightMap.md | 5 +- .../syntax/PQSMods/VertexHeightNoise.md | 2 + .../VertexHeightNoiseVertHeightCurve2.md | 2 + .../syntax/PQSMods/VertexSimplexHeight.md | 2 + .../PQSMods/VertexSimplexHeightAbsolute.md | 2 + .../syntax/PQSMods/VertexSimplexNoiseColor.md | 2 + 15 files changed, 254 insertions(+), 21 deletions(-) create mode 100644 wwwroot/content/syntax/PQSMods/AerialPerspectiveMaterial.md create mode 100644 wwwroot/content/syntax/PQSMods/AltitudeAlpha.md create mode 100644 wwwroot/content/syntax/PQSMods/Community/VertexMitchellNetravaliHeightMap.md diff --git a/wwwroot/content/syntax/PQSMods/AerialPerspectiveMaterial.md b/wwwroot/content/syntax/PQSMods/AerialPerspectiveMaterial.md new file mode 100644 index 0000000..ff98333 --- /dev/null +++ b/wwwroot/content/syntax/PQSMods/AerialPerspectiveMaterial.md @@ -0,0 +1,34 @@ +**Internal mod name:** `PQSMod_AerialPerspectiveMaterial` + +AerialPerspectiveMaterial is a peculiar PQSMod. It appears to have no effect on the vertex height or color of the planet. Instead, it seems to partially configure the material of the planet's surface. It is suspected that the intent of this mod is to update certain shader parameters on every frame so as to correct any atmospheric effects. + +## Example {#Example} +``` +PQS +{ + Mods + { + // Source: Dres (built-in) + AerialPerspectiveMaterial + { + atmosphereDepth = 150000 + globalDensity = -1E-05 + heightFalloff = 6.75 + oceanDepth = 0 + DEBUG_SetEveryFrame = True + order = 100 + enabled = True + } + } +} +``` + +## Properties {#Properties} + +|Property|Format|Description| +|--------|------|-----------| +|atmosphereDepth|Decimal|The altitude of the atmosphere in m.| +|globalDensity|Decimal|Unknown, presumed to be an atmosphere density multiplier.| +|heightFalloff|Decimal|The PQSMod appears to calculate atmospheric density at the altitude of the camera. This parameter seems to control the density falloff with altitude. The suspected behavior is `localDensity = exp(-heightFalloff * observerAltitude)`.| +|oceanDepth|Decimal|The depth of the ocean in m.| +|DEBUG_SetEveryFrame|Boolean|The parameters of this PQSMod are not expected to change dynamically. As such, it ususally suffices to set them only once when the planet gets loaded. Setting this parameter to true forces an update of `globalDensity`, `heightFalloff` and `atmosphereDepth` on every frame. \ No newline at end of file diff --git a/wwwroot/content/syntax/PQSMods/AltitudeAlpha.md b/wwwroot/content/syntax/PQSMods/AltitudeAlpha.md new file mode 100644 index 0000000..728286d --- /dev/null +++ b/wwwroot/content/syntax/PQSMods/AltitudeAlpha.md @@ -0,0 +1,28 @@ +**Internal mod name:** `PQSMod_AltitudeAlpha` + +AltitudeAlpha, much like [AerialPerspectiveMaterial](/Syntax/PQSMods/AerialPerspectiveMaterial), is a PQSMod that operates on mesh data for the sake of the terrain shader. While AerialPerspectiveMaterial seems to directly modify material properties, AltitudeAlpha (as its name implies) passes altitude data to the alpha channel of the color data of the mesh vertices. How this is used in the terrain shader is unknown. + +## Example {#Example} +``` +PQS +{ + Mods + { + // Source: Dres (built-in) + AltitudeAlpha + { + atmosphereDepth = 4000 + invert = False + order = 999999999 + enabled = False + } + } +} +``` + +## Properties {#Properties} + +|Property|Format|Description| +|--------|------|-----------| +|atmosphereDepth|Decimal|The sea-level-relative altitude of the vertex gets divided by this value to obtain the alpha value.| +|invert|Boolean|If true, the alpha data is inverted. By default, it has alpha=0 at sea level and alpha=1 at `atmosphereDepth`. This parameter inverts this gradient if need be.| \ No newline at end of file diff --git a/wwwroot/content/syntax/PQSMods/Community/VertexMitchellNetravaliHeightMap.md b/wwwroot/content/syntax/PQSMods/Community/VertexMitchellNetravaliHeightMap.md new file mode 100644 index 0000000..64d6daf --- /dev/null +++ b/wwwroot/content/syntax/PQSMods/Community/VertexMitchellNetravaliHeightMap.md @@ -0,0 +1,136 @@ +**Author**: [Niako](https://github.com/pkmniako) +**Source**: [GitHub Repository](https://github.com/pkmniako/Kopernicus_VertexMitchellNetravaliHeightMap) +**Download**: [Latest release on GitHub](https://github.com/pkmniako/Kopernicus_VertexMitchellNetravaliHeightMap/releases) +**Wiki**: [GitHub Wiki](https://github.com/pkmniako/Kopernicus_VertexMitchellNetravaliHeightMap/wiki) +**Bundling allowed**: Explicitly allowed +**Internal mod name**: `PQSMod_VertexMitchellNetravaliHeightMap` + +This is a community-contributed PQSMod that aims to solve a problem that exists with VertexHeightMap. Let's take Kerbin for example, a planet with a 600 km radius. This gives an equatorial circumference of approximately 3770 km. Even with 8K textures, every pixel will cover a width of approximately 0.46 km. Evidently Kerbals and their peculiar vehicles are much smaller than that, so for many vertices on the surface, they end up between adjacent pixels in the heightmap. + +To solve this, KSP uses bilinear interpolation: it figures out where the vertex is between the four surrounding heightmap pixels and linearly interpolates along the x and y axes of the image. This works, but it results in an unsightly world of slanted squares. This is typically hidden by adding additional noise PQSMods. + +On its face, the only way to get rid of this problem would be to increase the heightmap resolution until the squares become unnoticable. But if we settle for, for example, 0.25m sized squares (which would still be noticable), then for Kerbin alone this would require a heightmap with a width of 15 MILLION pixels, and a height of 7.54 million pixels. This results in an image containing 113.7 trillion (American English) or billion (British English) pixels. Using an L8-formatted DDS image, the expected filesize will therefore be 113.7 trillion bytes or 113 TB. + +But then it's only fitting; one would be capturing the surface detail of an entire celestial body. Clearly this won't do. + +Another solution, which is what Niako implemented in this PQSMod, uses better interpolating between the pixels. For example, what if we look at not just 2 pixels per axis (4 pixels total), but 4 pixels per axis (16 pixels total)? We may attempt to fit smoothened curves to the height data stored in the texture. The better this fit approximates the actual terrain contour, the closer the resulting interpolation will resemble the true intended terrain shape. + +An example fit is a cubic bezier spline, in which case one would be using the bicubic interpolation. Mitchell-Netravali interpolation is the result of a scientific investigation into image filtering, specifically artifacts produced by reconstruction filters. Put simply, it is an abstraction of spline based interpolation, with the spline behavior controlled by two parameters, B and C. For more information about B and C, as well as a helpful though suggestive plot of the behavior of various combinations of B and C, see the [Wikipedia article](https://en.wikipedia.org/wiki/Mitchell%E2%80%93Netravali_filters) on Mitchell-Netravali filters. + +Taken from this Wiki article, we shall now list a number of values for B and C that are used in practice. Notably, if B = 0, the equations reduce to that for a class of splines called Cardinal splines. + +|B|C|Spline name|Common implementations| +|0|Any|Cardinal splines|| +|0|0.5|Catmull-Rom Spline|Bicubic filter in GIMP| +|0|0.75|Unnamed Cardinal spline|Bicubic filter in Photoshop| +|1/3|1/3|Mitchell-Netravali|Mitchell filter in ImageMagick.| +|1|0|B-Spline|Bicubic filter in Paint.NET| + +Splines are a complicated subject. For a detailed explanation on what Cardinal, Catmull-Rom, Bezier and B-Splines are, we refer to an excellent [video by educator Freya Holmér](https://youtu.be/jvPPXbo87ds). Notable time stamps are: +- 44:10 for an explanation on Cardinal splines and the Catmull-Rom spline (which is a specific Cardinal spline). Freya first discusses the linear spline as a stepping stone toward Cardinal splines so the concept makes more sense. +- 53:15 for B-Splines. + +For completeness, this document also contains a brief explanation of these splines and the behavior you can expect from them, at the end. + +The upshot is that parameters B and C essentially control the smoothness of the interpolation (possibly at the expense of actually passing through the height values recorded in the heightmap). Careless configuration may introduce unwanted artifacts, such as rippling effects around high contrast edges (IE what ought to be cliffs in the terrain) or blurring. + +## Example {#Example} +You are allowed to bundle Mitchell-Netravali interpolation with your planet mods. In this case, define it as a substitute for VertexHeightMap: +``` +PQS +{ + Mods + { + VertexMitchellNetravaliHeightMap + { + map = ... + offset = ... + deformity = ... + scaleDeformityByRadius = False + order = ... + enabled = True + B = 1 + C = 0 + } + } +} +``` +But if you want to leave it up to the user, there is a trick you can do. When KSP imports a code library from a mod, ModuleManager picks up on it. As such, you can define config nodes if and only if a specific mod is installed, or if a specific mod is not installed. Using this, you can define both VertexHeightMap and VertexMitchellNetravaliHeightMap and have ModuleManager select one depending on whether Mitchell-Netravali interpolation is installed. + +``` +PQS +{ + Mods + { + // Source: Dres (Planet Cyran by The White Guardian) + VertexHeightMap:NEEDS[!VERTEXMITCHELLNETRAVALIHEIGHTMAP] + { + map = PlanetCyran/Kopernicus/PluginData/Dres_NewHeight.dds + offset = 0 + deformity = 8000 + scaleDeformityByRadius = False + order = 20 + enabled = True + } + VertexMitchellNetravaliHeightMap:NEEDS[VERTEXMITCHELLNETRAVALIHEIGHTMAP] + { + map = PlanetCyran/Kopernicus/PluginData/Dres_NewHeight.dds + offset = 0 + deformity = 8000 + scaleDeformityByRadius = False + order = 20 + enabled = True + B = 1 + C = 0 + } + } +} +``` + +## Properties {#Properties} +Most of the properties are identical to those of the built-in [VertexHeightMap](/Syntax/PQSMods/VertexHeightMap) PQSMod. The only difference is the parameters `B` and `C`. These are both decimal numbers that are expected to be in the [0, 1] range. + +## Appendix: Splines {#Appendix_Splines} + +To understand the Cardinal spline, it is necessary to first talk about two other kinds of splines: the Bezier spline (which you have probably used already every time you define a FloatCurve for a PQSMod or Atmosphere) and the Hermite spline. + +### Bezier Splines {#Bezier_Splines} +Interpolating linearly between points to connect them into a segmented line is a simple task, but it exhibits the behavior of heightmaps that we are looking to avoid: flat lines that connect harshly to create a jagged line. If we want to model, for example, the pressure-versus-altitude profile of a planetary atmosphere then this won't do. We need smoothness! + +What if, given two line segments and an interpolation parameter t, we interpolate linearly along both line segments, and then between the two interpolated points? So three interpolations in total? The result is a smoother curve called a quadratic Bézier curve. + +However, its bigger brother, the cubic Bézier curve, is more popular, where we have four points and three line segments, along which we interpolate to get three points, then another interpolation to get two points, and then one last interpolation to get the final position along the curve. For more points, we can use a higher degree Bezier curve, but there are two problems: +1. The resulting curve will only pass through the first and last points and just lazily follow the points in-between; +2. Moving a single point will affect a large portion of the curve. Being able to move a point and only affect the interpolation between that point and its nearest neighbours is called 'local control', and right now we don't have it, but we want to. After all, without it, the slightest adjustment would require adjusting every other control point, too. + +The solution is to connect Bézier curves together. This construction is called a Bézier spline. For the cubic Bézier spline, every control point has an in-tangent and an out-tangent. These are two 'ghost' points that assist in describing the trajectory of the curve between two adjacent control points. After all, for a cubic Bézier curve, we need four points to interpolate between. These four points, then, are control point A, the out-tangent of control point A, the in-tangent of control point B, and control point B itself. + +### Hermite Splines {#Hermite_Splines} +Using a cubic Bézier spline, we can now place the control points where we want the Bézier curve to pass through, and use the tangents to control the shape of the curve between the control points. But this becomes tedious if we are using a curve to describe motion. We will have control of position versus time, but velocity will be implicit. What if we instead define the curve in terms of control points with an outgoing velocity vector? This is the idea behind Hermite splines. Instead of pairs of tangent vectors, we define the position and velocity, as a more kinematic description of the curve's motion. + +### Cardinal Splines {#Cardinal_Splines} +But can we get even simpler? What if we go back to connecting two adjacent points with a straight line, and make a leap toward Hermite splines, by calculating velocity as the vector from point N to point N+2? By calculating 'velocity' implicitly, we should have a smooth curve passing through all control points that is solely defined by the positions of the control points. + +But in practice the resulting curves are a little strange. They mostly look like applying a teeny bit of smoothing to the straight line segments, with small and tight corners. We can relax the curve by scaling down the 'velocities' we calculated with a scaling factor, `s`. Setting `s` too small results in strong curvature at the joints with flattening along the segments. If `s=0`, we're left with linear interpolation. + +A special case is when `s = 0.5`. This Cardinal spline is known as the Catmull-Rom spline. + +### Continuity {#Continuity} +To explain what a B-Spline is, we first need to discuss a property of splines called continuity. All of the splines we discussed above form a continuous, uninterrupted line. In other words, the change in position is continuous and there are no jumps or 'teleporting' going on. + +This is called 'C0 continuity'. What, then, is C1 continuity? A spline achieves C1 continuity if there are no jumps in velocity either. A Hermite spline for example is always C0 and C1 continuous because it explicitly defines velocity at each point and interpolates the velocities of two adjacent control points. + +A Bézier spline is typically C0 continuous, but it need not be C1 continuous. A Bézier spline is C1 continuous if and only if the in-tangent and out-tangent of each control point are mirrored. This means that a C1 continuous Bézier spline essentially only has one tangent, the in-tangent, with the out-tangent being defined implicitly by the in-tangent. + +Or, in other words, enforcing C1 continuity on a Bézier spline turns it into something resembling a Hermite spline. This is no coincidence, a C1 continuous Bézier spline can be seamlessly converted into a Hermite spline and vice versa. + +But what of C2 continuity, meaning that there are no jumps in acceleration? This would result in an even smoother curve, but this is more difficult to achieve. Attempting to make a Bézier spline for example C2 compliant, creates some problems. If we enforce the constriant that the acceleration at the end of one spline section must equal the acceleration at the start of the next spline section, then the constraint that we end up with is that the in-tangent of the second control point of a spline segment is controlled by the in-tangent and the position of the section's first control point, as well as the out-tangent of the control point before that. + +An alert reader will notice something terrible occurring. We already gave up the ability to control both tangents to make the spline C1 continuous, but now all we're left with almost no control over the tangents. We can only control the in-tangent of the last control point, and the tangents for the first spline segment. All other tangents are defined implicitly in order to retain C2 continuity. The result is that we have given up local control. + +So, what if we instead attempt to create an entirely new spline, focused on being C0, C1 and C2 continuous? + +### B-Splines {#B_Splines} +We will skip the mathematics that leads to the construction of the B-Spline. The resulting spline is buttery smooth, but it does something that we haven't seen before: it isn't passing through the control points. We have given up that power in exchange for C2 continuity. + +A B-Spline is always C0, C1 and C2 continuous. Using a B-Spline to interpolate your heightmap gives a very, very smooth surface, but the altitude values may not exactly match what is described in the heightmap. \ No newline at end of file diff --git a/wwwroot/content/syntax/PQSMods/HeightColorMap.md b/wwwroot/content/syntax/PQSMods/HeightColorMap.md index 7ff5a05..0d322a1 100644 --- a/wwwroot/content/syntax/PQSMods/HeightColorMap.md +++ b/wwwroot/content/syntax/PQSMods/HeightColorMap.md @@ -1,4 +1,7 @@ -The `HeightColorMap` PQSMod is a mod that colors the terrain based on altitude using user-defined landclasses. +**Internal mod name:** `PQSMod_HeightColorMap` + +The `HeightColorMap` PQSMod is a mod that colors the terrain based on altitude using user-defined landclasses. A LandClass in this context is a color and an altitude range within which this color should be applied. + ## Subnodes {#Subnodes} * `LandClasses { }` (defined below) @@ -65,15 +68,15 @@ PQS |Property|Format|Description| |--------|------|-----------| -|blend|Decimal|The blend between the LandClasses.| +|blend|Decimal|This parameter controls the total blending of the output. In other words, if `blend = 0.7` then the result of the PQSMod is, at most, 70% of the mod's output and 30% of the original vertex color.| -## LandClasses {#landclasses} -The `LandClasses { }` wraps several `LandClass { }` nodes that describe an individual region's color as defined by altitudes. +## LandClasses {#LandClasses} +The `LandClasses { }` node wraps several `LandClass { }` subnodes that describe an individual region's color as defined by altitudes. |Property|Format|Description| |--------|------|-----------| |name|Text|The name of the LandClass.| -|color|Color|The color to be applied to the LandClass.| +|color|Color|The color to be applied to the terrain that falls within this LandClass.| |altitudeStart|Decimal|The starting altitude of the LandClass. NOTE: Altitude is measured in fractions of valid PQS height: `altitude = (vertexHeight - vertexMinHeightOfPQS) / vertexHeightDeltaOfPQS`.| |altitudeEnd|Decimal|The ending altitude of the LandClass. Follows same measurement unit as `altitudeStart`.| |lerpToNext|Boolean|Whether to blend into the next LandClass. Highly recommended to set to true on all but the last LandClass.| diff --git a/wwwroot/content/syntax/PQSMods/HeightColorMap2.md b/wwwroot/content/syntax/PQSMods/HeightColorMap2.md index 8a7cbe9..c995310 100644 --- a/wwwroot/content/syntax/PQSMods/HeightColorMap2.md +++ b/wwwroot/content/syntax/PQSMods/HeightColorMap2.md @@ -1,4 +1,6 @@ -The `HeightColorMap2` PQSMod is a mod that colors the terrain based on altitude using user-defined landclasses and has slightly more configuration options than [HeightColorMap](/pqsmods/heightcolormap). +**Internal mod name:** `PQSMod_HeightColorMap2` + +The `HeightColorMap2` PQSMod is a mod that colors the terrain based on altitude using user-defined landclasses and has slightly more configuration options than [HeightColorMap](/pqsmods/heightcolormap). The behavior is mostly identical, the difference lies in the two extra parameters: `minHeight` and `maxHeight`. Regular HeightColorMap normalizes the altitude using an inference of maximum terrain altitude. This PQSMod, by contrast, lets you specify the altitude range. ## Subnodes {#Subnodes} * `LandClasses { }` (defined below) @@ -68,7 +70,7 @@ PQS |Property|Format|Description| |--------|------|-----------| -|blend|Decimal|The blend between the LandClasses.| +|blend|Decimal|This parameter controls the total blending of the output. In other words, if `blend = 0.7` then the result of the PQSMod is, at most, 70% of the mod's output and 30% of the original vertex color.| |minHeight|Decimal|The minimum height, or `0.0` altitude, of a LandClass.| |maxHeight|Decimal|The maxmium height, or `1.0` altitude, of a LandClass.| @@ -78,7 +80,7 @@ The `LandClasses { }` wraps several `LandClass { }` nodes that describe an indiv |Property|Format|Description| |--------|------|-----------| |name|Text|The name of the LandClass.| -|color|Color|The color to be applied to the LandClass.| +|color|Color|The color to be applied to terrain that falls within the LandClass.| |altitudeStart|Decimal|The starting altitude of the LandClass. NOTE: Altitude is measured in fractions of defined height: `altitude = (height - minHeight) / (maxHeight - minHeight)`.| |altitudeEnd|Decimal|The ending altitude of the LandClass. Follows same measurement unit as `altitudeStart`.| |lerpToNext|Boolean|Whether to blend into the next LandClass. Highly recommended to set to true on all but the last LandClass.| diff --git a/wwwroot/content/syntax/PQSMods/LandControl/LandControl.md b/wwwroot/content/syntax/PQSMods/LandControl/LandControl.md index a909821..56fa933 100644 --- a/wwwroot/content/syntax/PQSMods/LandControl/LandControl.md +++ b/wwwroot/content/syntax/PQSMods/LandControl/LandControl.md @@ -1,3 +1,5 @@ +**Internal mod name:** `PQSLandControl` + The `LandControl` PQSMod allows defining regions known as LandClasses, within which you can customize several features of the particular region. ## Subnodes {#Subnodes} diff --git a/wwwroot/content/syntax/PQSMods/PQSMods.md b/wwwroot/content/syntax/PQSMods/PQSMods.md index 5b159a5..9ee6b31 100644 --- a/wwwroot/content/syntax/PQSMods/PQSMods.md +++ b/wwwroot/content/syntax/PQSMods/PQSMods.md @@ -8,15 +8,24 @@ Each PQSMod subnode contains `name`, `order`, and `enabled` keys, as described b |enabled|Boolean|Whether the PQSMod should be enabled.| |order|Integer|The order that the PQSMod should be processed in. PQSMods are processed in increasing `order` value, so a mod with `order` 20 would be applied before a mod with order `100`.| -## PQSMods {#pqsmods} (This is a non-exhaustive list, so contributions are especially welcome in this area!) -+ [LandControl](/Syntax/PQSMods/LandControl) -+ [HeightColorMap](/Syntax/PQSMods/HeightColorMap) -+ [HeightColorMap2](/Syntax/PQSMods/HeightColorMap2) -+ [VertexColorMap](/Syntax/PQSMods/VertexColorMap) -+ [VertexColorMapBlend](/Syntax/PQSMods/VertexColorMapBlend) -+ [VertexHeightMap](/Syntax/PQSMods/VertexHeightMap) -+ [VertexHeightNoise](/Syntax/PQSMods/VertexHeightNoise) -+ [VertexHeightNoiseVertHeightCurve2](/Syntax/PQSMods/VertexHeightNoiseVertHeightCurve2) -+ [VertexSimplexHeight](/Syntax/PQSMods/VertexSimplexHeight) -+ [VertexSimplexHeightAbsolute](/Syntax/PQSMods/VertexSimplexHeightAbsolute) -+ [VertexSimplexNoiseColor](/Syntax/PQSMods/VertexSimplexNoiseColor) +## PQSMods {#PQSMods} (This is a non-exhaustive list, so contributions are especially welcome in this area!) + +Note: **name** is the name you should be using in your configs. **Internal Name** is the name of the C# class that corresponds to this PQSMod. **To remove a specific mod in the Template node, refer to it by its internal name**. + +|Name|Internal Name| +|----|-------------| +|[AerialPerspectiveMaterial](/Syntax/PQSMods/AerialPerspectiveMaterial)|PQSMod_AerialPerspectiveMaterial| +|[HeightColorMap](/Syntax/PQSMods/HeightColorMap)|PQSMod_HeightColorMap| +|[HeightColorMap2](/Syntax/PQSMods/HeightColorMap2)|PQSMod_HeightColorMap2| +|[LandControl](/Syntax/PQSMods/LandControl)|PQSLandControl| +|[VertexColorMap](/Syntax/PQSMods/VertexColorMap)|PQSMod_VertexColorMap| +|[VertexColorMapBlend](/Syntax/PQSMods/VertexColorMapBlend)|PQSMod_VertexColorMapBlend| +|[VertexHeightMap](/Syntax/PQSMods/VertexHeightMap)|PQSMod_VertexHeightMap| +|[VertexHeightNoise](/Syntax/PQSMods/VertexHeightNoise)|PQSMod_VertexHeightNoise| +|[VertexHeightNoiseVertHeightCurve2](/Syntax/PQSMods/VertexHeightNoiseVertHeightCurve2)|PQSMod_VertexHeightNoiseVertHeightCurve2| +|[VertexSimplexHeight](/Syntax/PQSMods/VertexSimplexHeight)|PQSMod_VertexSimplexHeight| +|[VertexSimplexHeightAbsolute](/Syntax/PQSMods/VertexSimplexHeightAbsolute)|PQSMod_VertexSimplexHeightAbsolute| +|[VertexSimplexNoiseColor](/Syntax/PQSMods/VertexSimplexNoiseColor)|PQSMod_VertexSimplexNoiseColor| + +## Community PQSMods {#Community_PQSMods} +This section is a work-in-progress. diff --git a/wwwroot/content/syntax/PQSMods/VertexColorMap.md b/wwwroot/content/syntax/PQSMods/VertexColorMap.md index 6b4095d..c21334c 100644 --- a/wwwroot/content/syntax/PQSMods/VertexColorMap.md +++ b/wwwroot/content/syntax/PQSMods/VertexColorMap.md @@ -1,3 +1,5 @@ +**Internal mod name:** `PQSMod_VertexColorMap` + The `VertexColorMap` PQSMod is a mod that applies a color map over the terrain. ## Example {#Example} @@ -16,6 +18,7 @@ PQS } ``` +## Properties {#Properties} |Property|Format|Description| |--------|------|-----------| |map|File Path|The texture containing the color map for the body.| diff --git a/wwwroot/content/syntax/PQSMods/VertexColorMapBlend.md b/wwwroot/content/syntax/PQSMods/VertexColorMapBlend.md index f44ac60..5be2e49 100644 --- a/wwwroot/content/syntax/PQSMods/VertexColorMapBlend.md +++ b/wwwroot/content/syntax/PQSMods/VertexColorMapBlend.md @@ -1,3 +1,5 @@ +**Internal mod name:** `PQSMod_VertexColorMapBlend` + Like its alternative [VertexColorMap](/PQSMods/VertexColorMap), the `VertexColorMapBlend` PQSMod adds color to a body using a color map. However, this PQSMod "blends" in the color map to the existing texture by blending the edges of the color segment (i.e., between transparent and colored sections on the map). ## Example {#Example} @@ -18,6 +20,7 @@ PQS } ``` +## Properties {#Properties} |Property|Format|Description| |--------|------|-----------| |map|File Path|The path to the color map to use and blend.| diff --git a/wwwroot/content/syntax/PQSMods/VertexHeightMap.md b/wwwroot/content/syntax/PQSMods/VertexHeightMap.md index 3e02702..44e1b6d 100644 --- a/wwwroot/content/syntax/PQSMods/VertexHeightMap.md +++ b/wwwroot/content/syntax/PQSMods/VertexHeightMap.md @@ -1,3 +1,5 @@ +**Internal mod name:** `PQSMod_VertexHeightMap` + The `VertexHeightMap` PQSMod is a mod that *adds* a given height map to the terrain. This means that height mods are additive, i.e. heightmaps don't set a fixed height. ## Notes on Heightmaps {#offsetnote} @@ -34,9 +36,10 @@ PQS } ``` +## Properties {#Properties} |Property|Format|Description| |--------|------|-----------| |map|File Path|The texture containing the height map in greyscale. Black is the `offset` height, and White is the `deformity + offset` height.| |offset|Decimal|The offset of the height map.| |deformity|Decimal|The deformity of the height map (difference between lowest and highest point).| -|scaleDeformityByRadius|Boolean|Unknown| +|scaleDeformityByRadius|Boolean|Experimentation implies that this parameter is used only in the computation of the minimum and maximum altitudes of this PQSMod. Specifically, if true, then the `GetVertexMaxHeight` function increases proportional to the sphere's radius. The intended use of this is unknown.| diff --git a/wwwroot/content/syntax/PQSMods/VertexHeightNoise.md b/wwwroot/content/syntax/PQSMods/VertexHeightNoise.md index a5357a1..221b072 100644 --- a/wwwroot/content/syntax/PQSMods/VertexHeightNoise.md +++ b/wwwroot/content/syntax/PQSMods/VertexHeightNoise.md @@ -1,3 +1,5 @@ +**Internal mod name:** `PQSMod_VertexHeightNoise` + The `VertexHeightNoise` PQSMod is a mod that adds height noise to the terrain. This makes the terrain bumpier, though the "style" of bumps/features change with the noise type. The noise is also additive, meaning that instead of overwriting the terrain altitude, it simply adds or subtracts from it. diff --git a/wwwroot/content/syntax/PQSMods/VertexHeightNoiseVertHeightCurve2.md b/wwwroot/content/syntax/PQSMods/VertexHeightNoiseVertHeightCurve2.md index 05a8be2..ada4723 100644 --- a/wwwroot/content/syntax/PQSMods/VertexHeightNoiseVertHeightCurve2.md +++ b/wwwroot/content/syntax/PQSMods/VertexHeightNoiseVertHeightCurve2.md @@ -1,3 +1,5 @@ +**Internal mod name:** `PQSMod_VertexHeightNoiseVertHeightCurve2` + The `VertexHeightNoiseVertHeightCurve2` PQSMod is one of several mods in the HeightNoise family. They all produce heightmap noise, which can make terrain considerably more interesting. It is considered by some to be a much more customizable and far stabler alternative to [`VertexHeightNoise`](/PQSMods/VertexHeightNoise). diff --git a/wwwroot/content/syntax/PQSMods/VertexSimplexHeight.md b/wwwroot/content/syntax/PQSMods/VertexSimplexHeight.md index cacf48d..28198f0 100644 --- a/wwwroot/content/syntax/PQSMods/VertexSimplexHeight.md +++ b/wwwroot/content/syntax/PQSMods/VertexSimplexHeight.md @@ -1,3 +1,5 @@ +**Internal mod name:** `PQSMod_VertexSimplexHeight` + The `VertexSimplexHeight` PQSMod generates monochrome [Perlin noise](/Prerequisites/datatypes.md) for use in terrain deformation. ## Example {#Example} diff --git a/wwwroot/content/syntax/PQSMods/VertexSimplexHeightAbsolute.md b/wwwroot/content/syntax/PQSMods/VertexSimplexHeightAbsolute.md index 33ba38a..1c54cab 100644 --- a/wwwroot/content/syntax/PQSMods/VertexSimplexHeightAbsolute.md +++ b/wwwroot/content/syntax/PQSMods/VertexSimplexHeightAbsolute.md @@ -1,3 +1,5 @@ +**Internal mod name:** `PQSMod_VertexSimplexHeightAbsolute` + The `VertexSimplexHeightAbsolute` PQSMod conforms the terrain to a set height using simplex noise. ## Example {#Example} diff --git a/wwwroot/content/syntax/PQSMods/VertexSimplexNoiseColor.md b/wwwroot/content/syntax/PQSMods/VertexSimplexNoiseColor.md index 25505a5..43e33b8 100644 --- a/wwwroot/content/syntax/PQSMods/VertexSimplexNoiseColor.md +++ b/wwwroot/content/syntax/PQSMods/VertexSimplexNoiseColor.md @@ -1,3 +1,5 @@ +**Internal mod name:** `PQSMod_VertexSimplexNoiseColor` + The `VertexSimplexNoiseColor` PQSMod generates RGB [Perlin noise](/Prerequisites/datatypes) for use in terrain coloration. ## Example {#Example} From 4243cca7a7a2e4c7ef6c968459804a8ef8eb2b99 Mon Sep 17 00:00:00 2001 From: TWG Date: Wed, 31 Jul 2024 17:22:55 +0200 Subject: [PATCH 3/3] Update PQSMods.md --- wwwroot/content/syntax/PQSMods/PQSMods.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wwwroot/content/syntax/PQSMods/PQSMods.md b/wwwroot/content/syntax/PQSMods/PQSMods.md index 9ee6b31..c22811d 100644 --- a/wwwroot/content/syntax/PQSMods/PQSMods.md +++ b/wwwroot/content/syntax/PQSMods/PQSMods.md @@ -28,4 +28,6 @@ Note: **name** is the name you should be using in your configs. **Internal Name* |[VertexSimplexNoiseColor](/Syntax/PQSMods/VertexSimplexNoiseColor)|PQSMod_VertexSimplexNoiseColor| ## Community PQSMods {#Community_PQSMods} -This section is a work-in-progress. +|Name|Internal Name| +|----|-------------| +|[VertexMitchellNetravaliHeightMap](/Syntax/PQSMods/Community/VertexMitchellNetravaliHeightMap)|PQSMod_VertexMitchellNetravaliHeightMap|