├── LICENSE ├── README.md ├── consts.js ├── doc ├── blue_ks.png ├── mixture.png ├── white_ks.png └── yellow_ks.png ├── index.html ├── km.js └── serve /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Lars Wander 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kubelka-Munk in GLSL 2 | 3 | This repository contains a working implementation of Kubelka-Munk theory, but 4 | with fake K/S data. This being released as an educational guide, and to help 5 | those interested in developing their own imaginary pigments. 6 | 7 | ## Background 8 | 9 | [Kubelka-Munk theory](https://en.wikipedia.org/wiki/Kubelka%E2%80%93Munk_theory) 10 | models the color of a mixture of pigments. To do so, it uses information about 11 | the pigments in the form of their K (absorption) and S (scattering) curves. 12 | These curves relate wavelengths of light (typically in the visible spectrum) to 13 | the amount of light absorped or scattered. 14 | 15 | Kubelka-Munk theory is both effective and efficient when it comes to simulating 16 | pigment mixtures. However, it is difficult to acquire the needed K & S curves 17 | to perform this mixing accurately. Doing so by hand requires a lot of careful 18 | data collection and computation (see [this 19 | thesis](https://scholarworks.rit.edu/theses/4892/) for more information). In 20 | addition, the K & S curves are considered to be the IP of the paint 21 | manufacturer, so they are not keen to have them shared once known. 22 | 23 | This repository contains an implementation of Kubelka-Munk pigment mixing, but 24 | with fake spectral data for the supplied `WHITE`, `BLUE`, and `YELLOW` 25 | pigments. These fake spectral curves look like this: 26 | 27 |  28 | 29 |  30 | 31 |  32 | 33 | These approxmiate white, yellow, and blue, but when mixed still create grey: 34 | 35 |  36 | 37 | To perform more interesting or convincing mixing, better spectral data is 38 | needed. Feel free to contribute improved K/S curves if you develop them. __Do 39 | not__ contribute curves that may be considered IP. 40 | 41 | ## Running 42 | 43 | You can demo the pigment mixing in your browser. For convenience sake, the 44 | `serve` script runs a Python HTTP server in this directory: 45 | 46 | ```bash 47 | $ ./serve 48 | $ # open http://localhost:9119 in your browser 49 | ``` 50 | 51 | ## Contributing 52 | 53 | Try changing the colors (or adding more) in `consts.js`. These are interpreted 54 | and mixed in the fragment shader in `km.js`. 55 | 56 | ## Resources 57 | 58 | - Spectral data source: https://cie.co.at/data-tables. 59 | - Overview of KM mixing: https://scholarworks.rit.edu/theses/4892/ 60 | -------------------------------------------------------------------------------- /consts.js: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2023 Lars Wander 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a 6 | // copy of this software and associated documentation files (the "Software"), 7 | // to deal in the Software without restriction, including without limitation 8 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | // and/or sell copies of the Software, and to permit persons to whom the 10 | // Software is furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | // DEALINGS IN THE SOFTWARE. 22 | 23 | const SPD_MIN_NM = 380; 24 | const SPD_MAX_NM = 750; 25 | const SPD_STEP_SIZE_NM = 10; 26 | export const SPD_BUCKETS = (SPD_MAX_NM - SPD_MIN_NM) / SPD_STEP_SIZE_NM + 1; 27 | 28 | function assertBucketCount(curve, name) { 29 | if (curve.length !== SPD_BUCKETS) { 30 | throw new Error( 31 | `Invalid bucket count for ${name}: ${curve.length} != ${SPD_BUCKETS}` 32 | ); 33 | } 34 | } 35 | 36 | // CIE standard illuminant D65, in 10nm increments from 380-750nm. 37 | // https://en.wikipedia.org/wiki/Illuminant_D65 38 | // https://cie.co.at/data-tables 39 | const D65 = [49.9755, 54.6482, 82.7549, 91.486, 93.4318, 86.6823, 104.865, 40 | 117.008, 117.812, 114.861, 115.923, 108.811, 109.354, 107.802, 104.79, 41 | 107.689, 104.405, 104.046, 100, 96.3342, 95.788, 88.6856, 90.0062, 89.5991, 42 | 87.6987, 83.2886, 83.6992, 80.0268, 80.2146, 82.2778, 78.2842, 69.7213, 43 | 71.6091, 74.349, 61.604, 69.8856, 75.087, 63.5927]; 44 | 45 | assertBucketCount(D65, "D65 illuminant"); 46 | 47 | // CIElab standard observer functions, in 10nm increments from 380-750nm. 48 | // https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_standard_observer 49 | // https://cie.co.at/data-tables 50 | const X_ = [0.0002, 0.0024, 0.0191, 0.0847, 0.2045, 0.3147, 0.3837, 0.3707, 51 | 0.3023, 0.1956, 0.0805, 0.0162, 0.0038, 0.0375, 0.1177, 0.2365, 0.3768, 52 | 0.5298, 0.7052, 0.8787, 1.0142, 1.1185, 1.124, 1.0305, 0.8563, 0.6475, 53 | 0.4316, 0.2683, 0.1526, 0.0813, 0.0409, 0.0199, 0.0096, 0.0046, 0.0022, 54 | 0.001, 0.0005, 0.0003]; 55 | 56 | assertBucketCount(X_, "CIE X observer"); 57 | 58 | const Y_ = [0, 0.0003, 0.002, 0.0088, 0.0214, 0.0387, 0.0621, 0.0895, 0.1282, 59 | 0.1852, 0.2536, 0.3391, 0.4608, 0.6067, 0.7618, 0.8752, 0.962, 0.9918, 60 | 0.9973, 0.9556, 0.8689, 0.7774, 0.6583, 0.528, 0.3981, 0.2835, 0.1798, 61 | 0.1076, 0.0603, 0.0318, 0.0159, 0.0077, 0.0037, 0.0018, 0.0008, 0.0004, 62 | 0.0002, 0.0001]; 63 | 64 | assertBucketCount(Y_, "CIE Y observer"); 65 | 66 | const Z_ = [0.0007, 0.0105, 0.086, 0.3894, 0.9725, 1.5535, 1.9673, 1.9948, 67 | 1.7454, 1.3176, 0.7721, 0.4153, 0.2185, 0.112, 0.0607, 0.0305, 0.0137, 0.004, 68 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 69 | 70 | assertBucketCount(Z_, "CIE Z observer"); 71 | 72 | export const D65X = D65.map((v, i) => v * X_[i]); 73 | export const D65Y = D65.map((v, i) => v * Y_[i]); 74 | export const D65Z = D65.map((v, i) => v * Z_[i]); 75 | 76 | // Imaginary K/S curves. 77 | // K is the "absorption" and S is the "scattering" for the pigment. 78 | export const WHITE = { 79 | k: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 80 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 81 | s: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 82 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] , 83 | }; 84 | 85 | assertBucketCount(WHITE.k, "white K"); 86 | assertBucketCount(WHITE.k, "white S"); 87 | 88 | export const YELLOW = { 89 | k: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 91 | s: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 92 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] , 93 | } 94 | 95 | assertBucketCount(YELLOW.k, "yellow K"); 96 | assertBucketCount(YELLOW.s, "yellow S"); 97 | 98 | export const BLUE = { 99 | k: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 100 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 101 | s: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 102 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] , 103 | } 104 | 105 | assertBucketCount(BLUE.k, "blue K"); 106 | assertBucketCount(BLUE.s, "blue S"); 107 | -------------------------------------------------------------------------------- /doc/blue_ks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lwander/open-km/24222a131c94cdaf8cbbe76c0c228d44a536c99a/doc/blue_ks.png -------------------------------------------------------------------------------- /doc/mixture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lwander/open-km/24222a131c94cdaf8cbbe76c0c228d44a536c99a/doc/mixture.png -------------------------------------------------------------------------------- /doc/white_ks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lwander/open-km/24222a131c94cdaf8cbbe76c0c228d44a536c99a/doc/white_ks.png -------------------------------------------------------------------------------- /doc/yellow_ks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lwander/open-km/24222a131c94cdaf8cbbe76c0c228d44a536c99a/doc/yellow_ks.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |