├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── assets ├── UbuntuMono │ ├── UBUNTU FONT LICENCE.txt │ ├── UbuntuMono-B.ttf │ ├── UbuntuMono-BI.ttf │ ├── UbuntuMono-R.ttf │ └── UbuntuMono-RI.ttf └── stars.bin ├── file_list_GFC2019_datamask.txt ├── file_list_nasadem.txt ├── file_list_srtm3.txt ├── file_list_treecover.txt ├── generate ├── Cargo.toml └── src │ ├── download.rs │ ├── heightmap.rs │ ├── ktx2encode.rs │ ├── lib.rs │ ├── material.rs │ ├── noise.rs │ ├── sky │ ├── lut.rs │ ├── mod.rs │ └── precompute.rs │ └── textures.rs ├── planetcam ├── Cargo.toml └── src │ └── lib.rs ├── preview ├── Cargo.toml └── src │ └── main.rs ├── rshader ├── Cargo.toml └── src │ ├── dynamic_shaders.rs │ ├── lib.rs │ └── static_shaders.rs ├── rustfmt.toml ├── screenshot.png ├── src ├── astro.rs ├── billboards.rs ├── cache │ ├── generators.rs │ ├── layer.rs │ ├── mesh.rs │ ├── mod.rs │ └── tile.rs ├── compute_shader.rs ├── gpu_state.rs ├── lib.rs ├── mapfile.rs ├── shaders │ ├── atmosphere.glsl │ ├── bounding-sphere.comp │ ├── bounding-tree-billboards.comp │ ├── cull-meshes.comp │ ├── declarations.glsl │ ├── declarations.wgsl │ ├── gen-aerial-perspective.comp │ ├── gen-bent-normals.comp │ ├── gen-displacements.comp │ ├── gen-grass-canopy.comp │ ├── gen-grass.comp │ ├── gen-grass.wgsl │ ├── gen-heightmaps.comp │ ├── gen-materials.comp │ ├── gen-root-aerial-perspective.comp │ ├── gen-skyview.comp │ ├── gen-terrain-bounding.comp │ ├── gen-transmittance.comp │ ├── gen-tree-attributes.comp │ ├── gen-tree-billboards.wgsl │ ├── grass.frag │ ├── grass.vert │ ├── hash.glsl │ ├── model.frag │ ├── model.vert │ ├── pbr.glsl │ ├── shadowpass.frag │ ├── sky.frag │ ├── sky.vert │ ├── softdouble.glsl │ ├── stars.frag │ ├── stars.vert │ ├── terrain.frag │ ├── terrain.vert │ ├── tree-billboards.frag │ └── tree-billboards.vert ├── speedtree_xml.rs └── stream.rs ├── types ├── Cargo.toml └── src │ ├── lib.rs │ ├── math.rs │ └── node.rs ├── usgs_ned1.json ├── usgs_ned13.json └── usgs_ned19.json /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ main, next ] 6 | pull_request: 7 | branches: [ main, next ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | check: 14 | name: Check 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, windows-latest] 18 | runs-on: ${{ matrix.os }} 19 | steps: 20 | - uses: actions/checkout@v2 21 | - uses: actions-rs/toolchain@v1 22 | with: 23 | profile: minimal 24 | toolchain: stable 25 | override: true 26 | - name: Install dependencies 27 | if: ${{ matrix.os == 'ubuntu-latest' }} 28 | run: sudo apt-get update && sudo apt-get install -y libegl-dev libudev-dev 29 | - name: Check all targets 30 | uses: actions-rs/cargo@v1 31 | with: 32 | command: check 33 | args: --all-targets 34 | - name: Check generate feature 35 | uses: actions-rs/cargo@v1 36 | with: 37 | command: check 38 | args: -p terra-preview --features generate 39 | - name: Check smaa feature 40 | uses: actions-rs/cargo@v1 41 | with: 42 | command: check 43 | args: -p terra-preview --features smaa 44 | 45 | test: 46 | name: Test Suite 47 | runs-on: ubuntu-latest 48 | steps: 49 | - uses: actions/checkout@v2 50 | - uses: actions-rs/toolchain@v1 51 | with: 52 | profile: minimal 53 | toolchain: nightly 54 | override: true 55 | - name: Install dependencies 56 | run: sudo apt-get update && sudo apt-get install -y libegl-dev libudev-dev 57 | - uses: actions-rs/cargo@v1 58 | with: 59 | command: test 60 | 61 | fmt: 62 | name: Rustfmt 63 | runs-on: ubuntu-latest 64 | steps: 65 | - uses: actions/checkout@v2 66 | - uses: actions-rs/toolchain@v1 67 | with: 68 | profile: minimal 69 | toolchain: stable 70 | override: true 71 | - run: rustup component add rustfmt 72 | - uses: actions-rs/cargo@v1 73 | with: 74 | command: fmt 75 | args: --all -- --check 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | /rshader/target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 8 | Cargo.lock 9 | 10 | # Ignore Emacs backup files 11 | *~ 12 | 13 | .vscode -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jonathan Behrens "] 3 | categories = ["game-engines", "rendering"] 4 | description = "A rendering library for large scale terrains" 5 | documentation = "https://docs.rs/terra" 6 | edition = "2021" 7 | homepage = "https://github.com/fintelia/terra" 8 | license = "Apache-2.0" 9 | name = "terra" 10 | readme = "README.md" 11 | repository = "https://github.com/fintelia/terra" 12 | version = "0.3.0" 13 | resolver = "2" 14 | 15 | [workspace] 16 | members = ["generate", "planetcam", "preview", "rshader", "types"] 17 | default-members = [".", "preview"] 18 | 19 | [dependencies] 20 | anyhow = "1.0.70" 21 | atomicwrites = "0.4.0" 22 | bytemuck = { version = "1.13.1", features = ["extern_crate_alloc"] } 23 | cgmath = { version = "0.18.0", features = ["mint", "serde"], git = "https://github.com/rustgd/cgmath", rev = "d5e765db61cf9039cb625a789a59ddf6b6ab2337" } 24 | crossbeam = "0.8.2" 25 | dirs = "5.0.0" 26 | fnv = "1.0.7" 27 | futures = "0.3.27" 28 | hyper = { version = "0.14.25", features = ["http1"] } 29 | hyper-tls = "0.5.0" 30 | ktx2 = "0.3.0" 31 | lazy_static = "1.4.0" 32 | maplit = "1.0.2" 33 | mint = "0.5.9" 34 | num-traits = "0.2.15" 35 | quick-xml = { version = "0.28.1", features = ["serialize"] } 36 | rayon = "1.7.0" 37 | rshader = { path = "rshader", features = ["dynamic_shaders"] } 38 | serde = { version = "1.0.158", features = ["derive"] } 39 | tokio = { version = "1.26.0", features = ["fs", "macros", "sync", "rt", "rt-multi-thread", "io-util"] } 40 | terra-types = { path = "types" } 41 | vec_map = { version = "0.8.2", features = ["serde"] } 42 | wgpu = "0.15.1" 43 | zip = { version = "0.6.4", features = ["deflate"], default-features = false } 44 | zstd = "0.12.3" 45 | 46 | [dev-dependencies] 47 | approx = "0.5.1" 48 | 49 | [features] 50 | trace = ["wgpu/trace"] 51 | small-trace = ["trace"] 52 | 53 | [profile] 54 | [profile.dev] 55 | opt-level = 1 56 | 57 | [profile.dev.package."*"] 58 | opt-level = 3 59 | 60 | [profile.release] 61 | debug = true 62 | incremental = true 63 | 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terra [![crates.io](https://img.shields.io/crates/v/terra.svg)](https://crates.io/crates/terra) [![docs.rs](https://docs.rs/terra/badge.svg)](https://docs.rs/terra) [![Github CI](https://img.shields.io/github/workflow/status/fintelia/terra/Rust)](https://github.com/fintelia/terra/actions?query=workflow%3ARust) 2 | 3 | Terra is work in progress large scale terrain rendering library built on top of 4 | [wgpu](https://github.com/gfx-rs/wgpu). 5 | 6 | 7 | 8 | # Overview 9 | 10 | Terra supports rendering an entire planet with details ranging in scale from 11 | thousands of kilometers down to centimeters. In Terra, terrain is treated as a 12 | [heightmap](https://en.wikipedia.org/wiki/Heightmap) along with a collection of 13 | texture maps storing the surface normal, albedo, etc. 14 | 15 | All of this information can take quite a bit of space, so it isn't included in 16 | this repository. Instead, the necessary files are streamed from the internet at 17 | runtime and cached locally in a subdirectory with the current user's [cache 18 | directory](https://docs.rs/dirs/3.0.1/dirs/fn.cache_dir.html) (which for 19 | instance defaults to `~/.cache/terra` on Linux). 20 | 21 | ### Level of detail (LOD) 22 | 23 | To ensure smooth frame rates and avoid noticable "LOD popping", Terra internally 24 | uses sphere mapped version of the [Continuous Distance-Dependent Level of 25 | Detail](https://github.com/fstrugar/CDLOD/blob/master/cdlod_paper_latest.pdf) 26 | algorithm. 27 | 28 | ### Incremental Generation 29 | 30 | Terra works by streaming coarse grained tiles containing terrain attributes and then 31 | adding fractal details using wgpu compute shaders. 32 | 33 | # Getting Started 34 | 35 | [Install Rust](https://rustup.rs/) and then the other needed dependencies: 36 | 37 | ```bash 38 | $ sudo apt-get install libegl-dev libudev-dev build-essential libssl-dev cmake 39 | ``` 40 | 41 | Running should be as simple as: 42 | 43 | ```bash 44 | git clone git@github.com:fintelia/terra && cd terra 45 | cargo run --release 46 | ``` 47 | 48 | The first time you run Terra, it may take a minute or two to stream the necessary 49 | files. Don't worry if you have to kill the process part way through, on subsequent 50 | runs it will resume where it left off. 51 | 52 | Once that step is done, you should see the main Terra window. You can navigate 53 | with the arrow keeps, and increase/decrease your altitude via the Space and Z 54 | keys respectively. Joystick controls are also supported if one is detected. To 55 | exit, press Escape. 56 | 57 | You can also pass `--help` to see some other command line options. 58 | 59 | ### System Requirements 60 | 61 | * Windows or Linux operating system (Terra may work on MacOS but this hasn't been tested) 62 | * A fast internet connection 63 | * GPU with 2+ GB of VRAM 64 | 65 | # Data Sources / Credits 66 | 67 | During operation, this library downloads and merges datasets from a variety of sources. If you integrate 68 | it into your own project, please be sure to give proper credit to all of the following as applicable. 69 | 70 | | Kind | Source | 71 | | --- | --- | 72 | | Elevation | [ETOPO1 Global Relief Model](https://www.ngdc.noaa.gov/mgg/global) 73 | | Elevation | [NASA Digital Elevation Model](https://portal.opentopography.org/datasetMetadata?otCollectionID=OT.032021.4326.2) 74 | | Orthoimagery | [Blue Marble Next Generation](https://visibleearth.nasa.gov/view.php?id=76487) 75 | | Stars | [Yale Bright Star Catalog](http://tdc-www.harvard.edu/catalogs/bsc5.html) 76 | | Treecover | [Global Forest Change](https://data.globalforestwatch.org/documents/134f92e59f344549947a3eade9d80783/explore) 77 | | Trees | [SpeedTree](https://store.speedtree.com/) 78 | | Ground Textures | [FreePBR](https://freepbr.com/) 79 | -------------------------------------------------------------------------------- /assets/UbuntuMono/UBUNTU FONT LICENCE.txt: -------------------------------------------------------------------------------- 1 | ------------------------------- 2 | UBUNTU FONT LICENCE Version 1.0 3 | ------------------------------- 4 | 5 | PREAMBLE 6 | This licence allows the licensed fonts to be used, studied, modified and 7 | redistributed freely. The fonts, including any derivative works, can be 8 | bundled, embedded, and redistributed provided the terms of this licence 9 | are met. The fonts and derivatives, however, cannot be released under 10 | any other licence. The requirement for fonts to remain under this 11 | licence does not require any document created using the fonts or their 12 | derivatives to be published under this licence, as long as the primary 13 | purpose of the document is not to be a vehicle for the distribution of 14 | the fonts. 15 | 16 | DEFINITIONS 17 | "Font Software" refers to the set of files released by the Copyright 18 | Holder(s) under this licence and clearly marked as such. This may 19 | include source files, build scripts and documentation. 20 | 21 | "Original Version" refers to the collection of Font Software components 22 | as received under this licence. 23 | 24 | "Modified Version" refers to any derivative made by adding to, deleting, 25 | or substituting -- in part or in whole -- any of the components of the 26 | Original Version, by changing formats or by porting the Font Software to 27 | a new environment. 28 | 29 | "Copyright Holder(s)" refers to all individuals and companies who have a 30 | copyright ownership of the Font Software. 31 | 32 | "Substantially Changed" refers to Modified Versions which can be easily 33 | identified as dissimilar to the Font Software by users of the Font 34 | Software comparing the Original Version with the Modified Version. 35 | 36 | To "Propagate" a work means to do anything with it that, without 37 | permission, would make you directly or secondarily liable for 38 | infringement under applicable copyright law, except executing it on a 39 | computer or modifying a private copy. Propagation includes copying, 40 | distribution (with or without modification and with or without charging 41 | a redistribution fee), making available to the public, and in some 42 | countries other activities as well. 43 | 44 | PERMISSION & CONDITIONS 45 | This licence does not grant any rights under trademark law and all such 46 | rights are reserved. 47 | 48 | Permission is hereby granted, free of charge, to any person obtaining a 49 | copy of the Font Software, to propagate the Font Software, subject to 50 | the below conditions: 51 | 52 | 1) Each copy of the Font Software must contain the above copyright 53 | notice and this licence. These can be included either as stand-alone 54 | text files, human-readable headers or in the appropriate machine- 55 | readable metadata fields within text or binary files as long as those 56 | fields can be easily viewed by the user. 57 | 58 | 2) The font name complies with the following: 59 | (a) The Original Version must retain its name, unmodified. 60 | (b) Modified Versions which are Substantially Changed must be renamed to 61 | avoid use of the name of the Original Version or similar names entirely. 62 | (c) Modified Versions which are not Substantially Changed must be 63 | renamed to both (i) retain the name of the Original Version and (ii) add 64 | additional naming elements to distinguish the Modified Version from the 65 | Original Version. The name of such Modified Versions must be the name of 66 | the Original Version, with "derivative X" where X represents the name of 67 | the new work, appended to that name. 68 | 69 | 3) The name(s) of the Copyright Holder(s) and any contributor to the 70 | Font Software shall not be used to promote, endorse or advertise any 71 | Modified Version, except (i) as required by this licence, (ii) to 72 | acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with 73 | their explicit written permission. 74 | 75 | 4) The Font Software, modified or unmodified, in part or in whole, must 76 | be distributed entirely under this licence, and must not be distributed 77 | under any other licence. The requirement for fonts to remain under this 78 | licence does not affect any document created using the Font Software, 79 | except any version of the Font Software extracted from a document 80 | created using the Font Software may only be distributed under this 81 | licence. 82 | 83 | TERMINATION 84 | This licence becomes null and void if any of the above conditions are 85 | not met. 86 | 87 | DISCLAIMER 88 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 89 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 90 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF 91 | COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 92 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 93 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 94 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 95 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER 96 | DEALINGS IN THE FONT SOFTWARE. -------------------------------------------------------------------------------- /assets/UbuntuMono/UbuntuMono-B.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fintelia/terra/ba31d76c8e33d58f1327b6a128e2e9d870cc4933/assets/UbuntuMono/UbuntuMono-B.ttf -------------------------------------------------------------------------------- /assets/UbuntuMono/UbuntuMono-BI.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fintelia/terra/ba31d76c8e33d58f1327b6a128e2e9d870cc4933/assets/UbuntuMono/UbuntuMono-BI.ttf -------------------------------------------------------------------------------- /assets/UbuntuMono/UbuntuMono-R.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fintelia/terra/ba31d76c8e33d58f1327b6a128e2e9d870cc4933/assets/UbuntuMono/UbuntuMono-R.ttf -------------------------------------------------------------------------------- /assets/UbuntuMono/UbuntuMono-RI.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fintelia/terra/ba31d76c8e33d58f1327b6a128e2e9d870cc4933/assets/UbuntuMono/UbuntuMono-RI.ttf -------------------------------------------------------------------------------- /assets/stars.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fintelia/terra/ba31d76c8e33d58f1327b6a128e2e9d870cc4933/assets/stars.bin -------------------------------------------------------------------------------- /generate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "terra-generate" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | aligned-buf = { git = "https://github.com/fintelia/aligned-buf", rev = "daec48c6e868b194838b97772c9e018f259aab78" } 10 | anyhow = "1.0.70" 11 | atomicwrites = "0.4.0" 12 | bytemuck = { version = "1.13.1", features = ["extern_crate_alloc"] } 13 | cgmath = { version = "0.18.0", features = ["mint", "serde"], git = "https://github.com/rustgd/cgmath", rev = "d5e765db61cf9039cb625a789a59ddf6b6ab2337" } 14 | cogbuilder = { git = "https://github.com/fintelia/cogbuilder", rev = "24e491e823e446c0ddacef2fb5f797952867ff0f" } 15 | image = "0.24.5" 16 | imageproc = "0.23.0" 17 | itertools = "0.10.5" 18 | ktx2 = "0.3.0" 19 | lindel = "0.1.1" 20 | lru = "0.10.0" 21 | md5 = "0.7.0" 22 | num-traits = "0.2.15" 23 | rand = "0.8.5" 24 | rand_distr = "0.4.3" 25 | rayon = "1.7.0" 26 | reqwest = { version = "0.11.15", features = ["blocking"] } 27 | rust-s3 = { version = "0.32.3", features = ["blocking", "tokio"] } 28 | serde = { version = "1.0.158", features = ["derive"] } 29 | tiff = "0.9.0" 30 | terra-types = { path = "../types" } 31 | vrt-file = { git = "https://github.com/fintelia/vrt-file", rev = "6109f7f07561da1285f4a4c0c8cbbaf06b24381f" } 32 | zip = { version = "0.6.4", features = ["deflate"], default-features = false } 33 | zstd = "0.12.3" 34 | 35 | [dev-dependencies] 36 | approx = "0.5.1" 37 | -------------------------------------------------------------------------------- /generate/src/heightmap.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Error; 2 | use lru::LruCache; 3 | use std::collections::{HashMap, HashSet}; 4 | use std::hash::{Hash, Hasher}; 5 | use std::num::NonZeroUsize; 6 | use std::sync::{Arc, Condvar, Mutex, Weak}; 7 | 8 | #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] 9 | pub(crate) struct Sector { 10 | pub face: u8, 11 | pub x: u32, 12 | pub y: u32, 13 | } 14 | 15 | struct Cache { 16 | weak: HashMap>, 17 | strong: LruCache>, 18 | pending: HashSet, 19 | } 20 | impl Cache { 21 | fn new(capacity: usize) -> Self { 22 | Self { 23 | weak: HashMap::default(), 24 | strong: LruCache::new(NonZeroUsize::new(capacity).unwrap()), 25 | pending: HashSet::default(), 26 | } 27 | } 28 | fn get(&mut self, n: K) -> Option> { 29 | match self.strong.get_mut(&n) { 30 | Some(e) => Some(Arc::clone(&e)), 31 | None => match self.weak.get(&n)?.upgrade() { 32 | Some(t) => { 33 | self.strong.push(n, t.clone()); 34 | Some(Arc::clone(&t)) 35 | } 36 | None => { 37 | self.weak.remove(&n); 38 | None 39 | } 40 | }, 41 | } 42 | } 43 | fn is_pending(&self, n: &K) -> bool { 44 | self.pending.contains(n) 45 | } 46 | fn mark_pending(&mut self, n: K) { 47 | self.pending.insert(n); 48 | } 49 | fn insert(&mut self, n: K, a: Arc) { 50 | self.weak.insert(n, Arc::downgrade(&a)); 51 | self.strong.push(n, a); 52 | self.pending.remove(&n); 53 | } 54 | } 55 | 56 | pub(crate) struct CogTileCache { 57 | cache: Mutex>>>, 58 | condvars: Vec, 59 | cogs: Vec>, 60 | } 61 | impl CogTileCache { 62 | pub fn new(cogs: Vec>) -> Self { 63 | Self { 64 | cache: Mutex::new(Cache::new(128)), 65 | condvars: (0..256).map(|_| Condvar::new()).collect(), 66 | cogs, 67 | } 68 | } 69 | 70 | pub(crate) fn get( 71 | &self, 72 | layer: u8, 73 | face: u8, 74 | level: u8, 75 | tile: u32, 76 | ) -> Result>>, Error> { 77 | let key = (layer, face, level, tile); 78 | 79 | let mut cache = self.cache.lock().unwrap(); 80 | if let Some(sector) = cache.get(key) { 81 | return Ok(sector); 82 | } 83 | 84 | let mut hasher = std::collections::hash_map::DefaultHasher::new(); 85 | key.hash(&mut hasher); 86 | let hash = hasher.finish() as usize; 87 | 88 | if cache.is_pending(&key) { 89 | cache = self.condvars[hash % 256].wait_while(cache, |c| c.is_pending(&key)).unwrap(); 90 | if let Some(tile) = cache.get(key) { 91 | return Ok(tile); 92 | } 93 | } 94 | 95 | cache.mark_pending(key); 96 | drop(cache); 97 | 98 | let uncompressed = 99 | match self.cogs[layer as usize][face as usize].read_tile(level as u32, tile)? { 100 | Some(bytes) => Some(cogbuilder::decompress_tile(&bytes)?), 101 | None => None, 102 | }; 103 | let arc = Arc::new(uncompressed); 104 | 105 | let mut cache = self.cache.lock().unwrap(); 106 | cache.insert(key, arc.clone()); 107 | 108 | self.condvars[hash % 256].notify_all(); 109 | Ok(arc) 110 | } 111 | 112 | pub fn tiles_across(&self, layer: u8, level: u32) -> u32 { 113 | self.cogs[layer as usize][0].tiles_across(level) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /generate/src/ktx2encode.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Error; 2 | use bytemuck::Pod; 3 | use ktx2::Format; 4 | 5 | pub(crate) fn encode_ktx2( 6 | image_slices: &[Vec], 7 | width: u32, 8 | height: u32, 9 | depth: u32, 10 | layers: u32, 11 | format: ktx2::Format, 12 | ) -> Result, Error> { 13 | let samples: u16 = match format { 14 | Format::R8_UNORM | Format::R16_UNORM | Format::R32_SFLOAT => 1, 15 | Format::R8G8_UNORM | Format::R32G32_SFLOAT => 2, 16 | Format::R8G8B8A8_UNORM | Format::R32G32B32A32_SFLOAT => 4, 17 | Format::BC1_RGB_UNORM_BLOCK 18 | | Format::BC4_UNORM_BLOCK 19 | | Format::BC7_UNORM_BLOCK 20 | | Format::ASTC_4x4_UNORM_BLOCK => 1, 21 | Format::BC5_UNORM_BLOCK => 2, 22 | _ => unimplemented!("{:?}", format), 23 | }; 24 | let (compressed, bytes_per_block) = match format { 25 | Format::R8_UNORM | Format::R8G8_UNORM | Format::R8G8B8A8_UNORM => (false, samples), 26 | Format::R16_UNORM => (false, 2 * samples), 27 | Format::R32_SFLOAT | Format::R32G32_SFLOAT | Format::R32G32B32A32_SFLOAT => { 28 | (false, 4 * samples) 29 | } 30 | Format::BC1_RGB_UNORM_BLOCK | Format::BC4_UNORM_BLOCK => (true, 8), 31 | Format::BC7_UNORM_BLOCK | Format::ASTC_4x4_UNORM_BLOCK | Format::BC5_UNORM_BLOCK => { 32 | (true, 16) 33 | } 34 | _ => unimplemented!(), 35 | }; 36 | let levels = image_slices.len() as u32; 37 | let dfd_size = 28u32 + 16 * samples as u32; 38 | 39 | let mut contents = Vec::new(); 40 | contents.extend_from_slice(&[ 41 | 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x32, 0x30, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A, 42 | ]); 43 | contents.extend_from_slice(&format.0.get().to_le_bytes()); 44 | contents.extend_from_slice(&1u32.to_le_bytes()); 45 | contents.extend_from_slice(&width.to_le_bytes()); 46 | contents.extend_from_slice(&height.to_le_bytes()); 47 | contents.extend_from_slice(&depth.to_le_bytes()); 48 | contents.extend_from_slice(&layers.to_le_bytes()); 49 | contents.extend_from_slice(&1u32.to_le_bytes()); // faces 50 | contents.extend_from_slice(&levels.to_le_bytes()); // levels 51 | contents.extend_from_slice(&2u32.to_le_bytes()); // supercompressionScheme = zstd 52 | 53 | contents.extend_from_slice(&(80 + 24 * levels).to_le_bytes()); 54 | contents.extend_from_slice(&dfd_size.to_le_bytes()); 55 | contents.extend_from_slice(&0u32.to_le_bytes()); // kvdByteOffset 56 | contents.extend_from_slice(&0u32.to_le_bytes()); // kvdByteLength 57 | contents.extend_from_slice(&0u64.to_le_bytes()); // sgdByteOffset 58 | contents.extend_from_slice(&0u64.to_le_bytes()); // sgdByteLength 59 | assert_eq!(contents.len(), 80); 60 | 61 | let mut compressed_image_slices = Vec::new(); 62 | let mut offset = (80 + 24 * levels + dfd_size) as u64; 63 | for image_slice in image_slices { 64 | let compressed = zstd::encode_all(std::io::Cursor::new(image_slice), 12).unwrap(); 65 | contents.extend_from_slice(&offset.to_le_bytes()); 66 | contents.extend_from_slice(&(compressed.len() as u64).to_le_bytes()); 67 | contents.extend_from_slice(&(image_slice.len() as u64).to_le_bytes()); 68 | offset += compressed.len() as u64; 69 | compressed_image_slices.push(compressed); 70 | } 71 | 72 | assert_eq!(contents.len(), 80 + 24 * levels as usize); 73 | contents.extend_from_slice(&dfd_size.to_le_bytes()); 74 | contents.extend_from_slice(&0u32.to_le_bytes()); // vendor ID + descriptor type 75 | contents.extend_from_slice(&2u16.to_le_bytes()); // version number 76 | contents.extend_from_slice(&(24u16 + 16 * samples).to_le_bytes()); // descriptor block size 77 | contents.push(1); // model 78 | contents.push(1); // color primaries 79 | contents.push(1); // transfer function (1 = linear, 2 = sRGB) 80 | contents.push(0); // flags (1 = premultiplied alpha) 81 | contents.push(if compressed { 3 } else { 0 }); // texel block dimension0 (0 = 1x1 block, 3 = 4x4 block) 82 | contents.push(if compressed { 3 } else { 0 }); // texel block dimension1 (0 = 1x1 block, 3 = 4x4 block) 83 | contents.push(0); // texel block dimension2 84 | contents.push(0); // texel block dimension3 85 | contents.push(bytes_per_block as u8); // bytes plane0 86 | contents.push(0); // bytes plane1 87 | contents.push(0); // bytes plane2 88 | contents.push(0); // bytes plane3 89 | contents.push(0); // bytes plane4 90 | contents.push(0); // bytes plane5 91 | contents.push(0); // bytes plane6 92 | contents.push(0); // bytes plane7 93 | 94 | match format { 95 | Format::R8_UNORM | Format::R8G8_UNORM | Format::R8G8B8A8_UNORM => { 96 | for i in 0..samples { 97 | contents.extend_from_slice(&(i as u16 * 8).to_le_bytes()); // bitOffset 98 | contents.push(7); // bitLength 99 | contents.push(if i == 3 { 0x1F } else { i as u8 }); // channelType + F[loat] + S[igned] + E[ponent] + L[inear] 100 | contents.extend_from_slice(&[0; 4]); // samplePosition[0..3] 101 | contents.extend_from_slice(&0u32.to_le_bytes()); // sampleLower 102 | contents.extend_from_slice(&255u32.to_le_bytes()); // sampleUpper 103 | } 104 | } 105 | Format::R16_UNORM => { 106 | for i in 0..samples { 107 | contents.extend_from_slice(&(i as u16 * 8).to_le_bytes()); // bitOffset 108 | contents.push(15); // bitLength 109 | contents.push(if i == 3 { 0x1F } else { i as u8 }); // channelType + F[loat] + S[igned] + E[ponent] + L[inear] 110 | contents.extend_from_slice(&[0; 4]); // samplePosition[0..3] 111 | contents.extend_from_slice(&0u32.to_le_bytes()); // sampleLower 112 | contents.extend_from_slice(&65535u32.to_le_bytes()); // sampleUpper 113 | } 114 | } 115 | Format::R32_SFLOAT | Format::R32G32_SFLOAT | Format::R32G32B32A32_SFLOAT => { 116 | for i in 0..samples { 117 | contents.extend_from_slice(&(i as u16 * 32).to_le_bytes()); // bitOffset 118 | contents.push(31); // bitLength 119 | contents.push(if i == 3 { 0b1101_1111 } else { 0b1100_0000 | i as u8 }); // channelType + F[loat] + S[igned] + E[ponent] + L[inear] 120 | contents.extend_from_slice(&[0; 4]); // samplePosition[0..3] 121 | contents.extend_from_slice(&0u32.to_le_bytes()); // sampleLower 122 | contents.extend_from_slice(&u32::MAX.to_le_bytes()); // sampleUpper 123 | } 124 | } 125 | Format::BC1_RGB_UNORM_BLOCK 126 | | Format::BC4_UNORM_BLOCK 127 | | Format::BC7_UNORM_BLOCK 128 | | Format::ASTC_4x4_UNORM_BLOCK => { 129 | contents.extend_from_slice(&0u16.to_le_bytes()); // bitOffset 130 | contents.push(bytes_per_block as u8 * 8 - 1); // bitLength 131 | contents.push(0); // channelType + F[loat] + S[igned] + E[ponent] + L[inear] 132 | contents.extend_from_slice(&[0; 4]); // samplePosition[0..3] 133 | contents.extend_from_slice(&0u32.to_le_bytes()); // sampleLower 134 | contents.extend_from_slice(&u32::MAX.to_le_bytes()); // sampleUpper 135 | } 136 | Format::BC5_UNORM_BLOCK => { 137 | for i in 0..2 { 138 | contents.extend_from_slice(&(i as u16 * 64).to_le_bytes()); // bitOffset 139 | contents.push(63); // bitLength 140 | contents.push(i); // channelType + F[loat] + S[igned] + E[ponent] + L[inear] 141 | contents.extend_from_slice(&[0; 4]); // samplePosition[0..3] 142 | contents.extend_from_slice(&0u32.to_le_bytes()); // sampleLower 143 | contents.extend_from_slice(&u32::MAX.to_le_bytes()); // sampleUpper 144 | } 145 | } 146 | _ => unimplemented!(), 147 | } 148 | 149 | if contents.len() % 4 != 0 { 150 | contents.resize((contents.len() & !3) + 4, 0); 151 | } 152 | 153 | assert_eq!(contents.len(), 80 + 24 * levels as usize + dfd_size as usize); 154 | for image_slice in compressed_image_slices { 155 | contents.extend_from_slice(&image_slice); 156 | } 157 | 158 | Ok(contents) 159 | } 160 | 161 | pub(crate) fn encode_ktx2_simple( 162 | data: &[T], 163 | width: u32, 164 | height: u32, 165 | format: ktx2::Format, 166 | ) -> Result, Error> { 167 | encode_ktx2(&[bytemuck::cast_slice(data).to_vec()], width, height, 0, 0, format) 168 | } 169 | -------------------------------------------------------------------------------- /generate/src/material.rs: -------------------------------------------------------------------------------- 1 | // use image::GenericImageView; 2 | 3 | // #[derive(Clone, Copy)] 4 | // pub enum MaterialType { 5 | // Dirt = 0, 6 | // Grass = 1, 7 | // GrassRocky = 2, 8 | // Rock = 3, 9 | // RockSteep = 4, 10 | // } 11 | 12 | // pub(super) fn high_pass_filter(albedo_image: &mut image::RgbImage) { 13 | // let resolution = 2048; 14 | 15 | // let albedo_image_blurred = { 16 | // let sigma = 8; 17 | // let tiled = 18 | // image::RgbImage::from_fn(resolution + 4 * sigma, resolution + 4 * sigma, |x, y| { 19 | // *albedo_image.get_pixel( 20 | // (x + resolution - 2 * sigma) % resolution, 21 | // (y + resolution - 2 * sigma) % resolution, 22 | // ) 23 | // }); 24 | // let mut tiled = image::DynamicImage::ImageRgb8(tiled).blur(sigma as f32); 25 | // tiled.crop(2 * sigma, 2 * sigma, resolution, resolution) 26 | // }; 27 | 28 | // let mut albedo_sum = [0u64; 3]; 29 | // for color in albedo_image.pixels() { 30 | // for i in 0..3 { 31 | // albedo_sum[i] += color[i] as u64; 32 | // } 33 | // } 34 | // let num_pixels = (albedo_image.width() * albedo_image.height()) as u64; 35 | // let average_albedo: [u8; 3] = [ 36 | // (albedo_sum[0] / num_pixels) as u8, 37 | // (albedo_sum[1] / num_pixels) as u8, 38 | // (albedo_sum[2] / num_pixels) as u8, 39 | // ]; 40 | 41 | // for (x, y, blurred_color) in albedo_image_blurred.pixels() { 42 | // let mut color = *albedo_image.get_pixel(x, y); 43 | // for i in 0..3 { 44 | // let c = (color[i] as i16) - (blurred_color[i] as i16) + (average_albedo[i] as i16); 45 | // color[i] = if c < 0 { 46 | // 0 47 | // } else if c > 255 { 48 | // 255 49 | // } else { 50 | // c 51 | // } as u8; 52 | // } 53 | // albedo_image.put_pixel(x, y, color); 54 | // } 55 | // } 56 | 57 | // struct MaterialTypeFiltered(MaterialType); 58 | // impl GeneratedAsset for MaterialTypeFiltered { 59 | // type Type = Material; 60 | 61 | // fn filename(&self) -> String { 62 | // let name = match self.0 { 63 | // MaterialType::Dirt => "dirt.bin", 64 | // MaterialType::Grass => "grass.bin", 65 | // MaterialType::GrassRocky => "grassrocky.bin", 66 | // MaterialType::Rock => "rock.bin", 67 | // MaterialType::RockSteep => "rocksteep.bin", 68 | // }; 69 | // format!("materials/filtered/{}", name) 70 | // } 71 | 72 | // fn generate(&self, context: &mut AssetLoadContext) -> Result { 73 | // context.set_progress_and_total(0, 7); 74 | 75 | // let resolution = 1024; 76 | // let mipmaps = 11; 77 | 78 | // let raw = MaterialTypeRaw(self.0).load(context)?; 79 | // let mut albedo_image = 80 | // image::DynamicImage::ImageRgba8(image::load_from_memory(&raw.albedo[..])?.to_rgba()); 81 | // if albedo_image.width() != resolution || albedo_image.height() != resolution { 82 | // albedo_image = 83 | // albedo_image.resize_exact(resolution, resolution, image::FilterType::Triangle); 84 | // } 85 | 86 | // let albedo_image_blurred = { 87 | // let sigma = 8; 88 | // context.set_progress(1); 89 | // let tiled = image::RgbaImage::from_fn( 90 | // resolution + 4 * sigma, 91 | // resolution + 4 * sigma, 92 | // |x, y| { 93 | // albedo_image.get_pixel( 94 | // (x + resolution - 2 * sigma) % resolution, 95 | // (y + resolution - 2 * sigma) % resolution, 96 | // ) 97 | // }, 98 | // ); 99 | // context.set_progress(2); 100 | // let mut tiled = image::DynamicImage::ImageRgba8(tiled).blur(sigma as f32); 101 | // context.set_progress(3); 102 | // tiled.crop(2 * sigma, 2 * sigma, resolution, resolution) 103 | // }; 104 | 105 | // context.set_progress(4); 106 | // let mut albedo_sum = [0u64; 4]; 107 | // for (_, _, color) in albedo_image.pixels() { 108 | // for i in 0..4 { 109 | // albedo_sum[i] += color[i] as u64; 110 | // } 111 | // } 112 | // let num_pixels = (albedo_image.width() * albedo_image.height()) as u64; 113 | // let average_albedo: [u8; 4] = [ 114 | // (albedo_sum[0] / num_pixels) as u8, 115 | // (albedo_sum[1] / num_pixels) as u8, 116 | // (albedo_sum[2] / num_pixels) as u8, 117 | // (albedo_sum[3] / num_pixels) as u8, 118 | // ]; 119 | 120 | // context.set_progress(5); 121 | // for (x, y, blurred_color) in albedo_image_blurred.pixels() { 122 | // let mut color = albedo_image.get_pixel(x, y); 123 | // for i in 0..4 { 124 | // use image::math::utils::clamp; 125 | // color[i] = clamp( 126 | // (color[i] as i16) - (blurred_color[i] as i16) + (average_albedo[i] as i16), 127 | // 0, 128 | // 255, 129 | // ) as u8; 130 | // } 131 | // albedo_image.put_pixel(x, y, color); 132 | // } 133 | 134 | // context.set_progress(6); 135 | // let mut albedo = Vec::new(); 136 | // for level in 0..mipmaps { 137 | // let level_resolution = (resolution >> level) as u32; 138 | // if albedo_image.width() != level_resolution || albedo_image.height() != level_resolution 139 | // { 140 | // albedo_image = albedo_image.resize_exact( 141 | // level_resolution, 142 | // level_resolution, 143 | // image::FilterType::Triangle, 144 | // ); 145 | // } 146 | 147 | // albedo.push( 148 | // albedo_image.to_rgba().to_vec()[..] 149 | // .chunks(4) 150 | // .map(|c| [c[0], c[1], c[2], c[3]]) 151 | // .collect(), 152 | // ); 153 | // } 154 | // context.set_progress(7); 155 | // Ok(Material { 156 | // resolution: resolution as u16, 157 | // mipmaps, 158 | // albedo, 159 | // }) 160 | // } 161 | // } 162 | 163 | // /// Holds the raw bytes of the image files for each map of a material. 164 | // #[derive(Serialize, Deserialize, Default)] 165 | // struct MaterialRaw { 166 | // albedo: Vec, 167 | // } 168 | 169 | // #[derive(Serialize, Deserialize)] 170 | // struct Material { 171 | // resolution: u16, 172 | // mipmaps: u8, 173 | 174 | // albedo: Vec>, 175 | // } 176 | 177 | // pub struct MaterialSet { 178 | // pub(crate) texture_view: gfx_core::handle::ShaderResourceView, 179 | // pub(crate) _texture: gfx_core::handle::Texture, 180 | // average_albedos: Vec<[u8; 4]>, 181 | // } 182 | 183 | // impl MaterialSet { 184 | // pub(crate) fn load, C: gfx_core::command::Buffer>( 185 | // factory: &mut F, 186 | // encoder: &mut gfx::Encoder, 187 | // context: &mut AssetLoadContext, 188 | // ) -> Result { 189 | // let resolution = 1024; 190 | // let mipmaps = 11; 191 | 192 | // let materials = vec![ 193 | // MaterialTypeFiltered(MaterialType::Dirt).load(context)?, 194 | // MaterialTypeFiltered(MaterialType::Grass).load(context)?, 195 | // MaterialTypeFiltered(MaterialType::GrassRocky).load(context)?, 196 | // MaterialTypeFiltered(MaterialType::Rock).load(context)?, 197 | // MaterialTypeFiltered(MaterialType::RockSteep).load(context)?, 198 | // ]; 199 | 200 | // let mut average_albedos = Vec::new(); 201 | 202 | // let texture = factory 203 | // .create_texture::( 204 | // gfx::texture::Kind::D2Array( 205 | // resolution, 206 | // resolution, 207 | // materials.len() as u16, 208 | // gfx::texture::AaMode::Single, 209 | // ), 210 | // mipmaps, 211 | // gfx::memory::Bind::SHADER_RESOURCE, 212 | // gfx::memory::Usage::Dynamic, 213 | // Some(ChannelType::Srgb), 214 | // ).unwrap(); 215 | 216 | // for (layer, material) in materials.iter().enumerate() { 217 | // assert_eq!(mipmaps, material.mipmaps); 218 | // assert_eq!(mipmaps as usize, material.albedo.len()); 219 | 220 | // for (level, albedo) in material.albedo.iter().enumerate() { 221 | // encoder 222 | // .update_texture::( 223 | // &texture, 224 | // None, 225 | // gfx_core::texture::NewImageInfo { 226 | // xoffset: 0, 227 | // yoffset: 0, 228 | // zoffset: layer as u16, 229 | // width: resolution >> level, 230 | // height: resolution >> level, 231 | // depth: 1, 232 | // format: (), 233 | // mipmap: level as u8, 234 | // }, 235 | // &albedo[..], 236 | // ).unwrap(); 237 | // } 238 | // average_albedos.push(material.albedo.last().unwrap()[0]); 239 | // } 240 | 241 | // // TODO: get rid of this hack. 242 | // let s = |v: &mut u8, s: f32| *v = LINEAR_TO_SRGB[(SRGB_TO_LINEAR[*v] as f32 * s) as u8]; 243 | // for i in 0..4 { 244 | // s(&mut average_albedos[MaterialType::Rock as usize][i], 0.5); 245 | // s( 246 | // &mut average_albedos[MaterialType::RockSteep as usize][i], 247 | // 0.4, 248 | // ); 249 | // } 250 | 251 | // let texture_view = factory 252 | // .view_texture_as_shader_resource::( 253 | // &texture, 254 | // (0, mipmaps), 255 | // Swizzle::new(), 256 | // ).unwrap(); 257 | 258 | // Ok(Self { 259 | // texture_view, 260 | // _texture: texture, 261 | // average_albedos, 262 | // }) 263 | // } 264 | 265 | // pub(crate) fn get_average_albedo(&self, material: MaterialType) -> [u8; 4] { 266 | // self.average_albedos[material as usize].clone() 267 | // } 268 | // } 269 | -------------------------------------------------------------------------------- /generate/src/noise.rs: -------------------------------------------------------------------------------- 1 | use rand::distributions::Distribution; 2 | use rand_distr::Normal; 3 | 4 | pub struct Heightmap { 5 | pub heights: Vec, 6 | pub width: u16, 7 | pub height: u16, 8 | } 9 | 10 | /// Evaluate wavelet noise on a grid with the given resolution and grid spacing. /// 11 | /// The output heightmap will have a width and height of `grid_resolution` * `grid_spacing`. Values 12 | /// will have a mean of approximately zero, and a variance of 1. 13 | pub fn wavelet_noise(grid_resolution: usize, grid_spacing: usize) -> Heightmap { 14 | // See: https://graphics.pixar.com/library/WaveletNoise/paper.pdf 15 | 16 | fn modulo(x: i32, n: usize) -> usize { 17 | let m = x % n as i32; 18 | if m < 0 { 19 | (m + n as i32) as usize 20 | } else { 21 | m as usize 22 | } 23 | } 24 | fn downsample(from: &[f32], to: &mut [f32], n: usize, stride: usize) { 25 | const ARAD: i32 = 16; 26 | #[cfg_attr(rustfmt, rustfmt_skip)] 27 | const COEFFS: [f32; 32] = [ 28 | 0.000334,-0.001528, 0.000410, 0.003545,-0.000938,-0.008233, 0.002172, 0.019120, 29 | -0.005040,-0.044412, 0.011655, 0.103311,-0.025936,-0.243780, 0.033979, 0.655340, 30 | 0.655340, 0.033979,-0.243780,-0.025936, 0.103311, 0.011655,-0.044412,-0.005040, 31 | 0.019120, 0.002172,-0.008233,-0.000938, 0.003546, 0.000410,-0.001528, 0.000334 32 | ]; 33 | for i in 0..(n / 2) { 34 | to[i * stride] = 0.0; 35 | let min_k = 2 * (i as i32) - ARAD; 36 | let max_k = 2 * (i as i32) + ARAD; 37 | for k in min_k..(max_k) { 38 | let index = (ARAD + k - 2 * i as i32) as usize; 39 | to[i * stride] += COEFFS[index] * from[modulo(k, n) * stride]; 40 | } 41 | } 42 | } 43 | fn upsample(from: &[f32], to: &mut [f32], n: usize, stride: usize) { 44 | let p_coeffs = [0.25, 0.75, 0.75, 0.25]; 45 | for i in 0..n { 46 | to[i * stride] = 0.0; 47 | for k in (i / 2)..(i / 2 + 2) { 48 | to[i * stride] += p_coeffs[2 + i - 2 * k] * from[(k % (n / 2)) * stride]; 49 | } 50 | } 51 | } 52 | fn generate_noise_tile(n: usize) -> Vec { 53 | assert!(n % 2 == 0); // size must be even! 54 | 55 | let mut temp1 = vec![0.0; n * n]; 56 | let mut temp2 = vec![0.0; n * n]; 57 | let mut noise = Vec::new(); 58 | 59 | // Step 1. Fill the tile with random numbers in the range -1 to 1. 60 | let normal = Normal::new(0.0, 1.0).unwrap(); 61 | for _ in 0..(n * n) { 62 | noise.push(normal.sample(&mut rand::thread_rng()) as f32); 63 | } 64 | 65 | // Steps 2 and 3. Downsample and upsample the tile 66 | for iy in 0..n { 67 | // each x row 68 | let i = iy * n; 69 | downsample(&noise[i..], &mut temp1[i..], n, 1); 70 | upsample(&temp1[i..], &mut temp2[i..], n, 1); 71 | } 72 | for ix in 0..n { 73 | // each y row 74 | let i = ix; 75 | downsample(&temp2[i..], &mut temp1[i..], n, n); 76 | upsample(&temp1[i..], &mut temp2[i..], n, n); 77 | } 78 | 79 | // Step 4. Subtract out the coarse-scale contribution 80 | for i in 0..(n * n) { 81 | noise[i] -= temp2[i]; 82 | } 83 | // Avoid even/odd variance difference by adding odd-offset version of noise to itself. 84 | let mut offset = n / 2; 85 | if offset % 2 == 0 { 86 | offset += 1; 87 | } 88 | let mut i = 0; 89 | for ix in 0..n { 90 | for iy in 0..n { 91 | temp1[i] = noise[((ix + offset) % n) + ((iy + offset) % n) * n]; 92 | i += 1; 93 | } 94 | } 95 | for i in 0..(n * n) { 96 | noise[i] += temp1[i]; 97 | } 98 | 99 | noise 100 | } 101 | 102 | /* Non-projected 2D noise */ 103 | fn noise(noise_tile: &[f32], n: usize, p: [f32; 2]) -> f32 { 104 | let mut mid = [0; 2]; 105 | let mut w = [[0.0; 3]; 2]; 106 | 107 | /* Evaluate quadratic B-spline basis functions */ 108 | for i in 0..2 { 109 | let center = p[i] - 0.5; 110 | let t = center.ceil() - center; 111 | 112 | mid[i] = center.ceil() as i32; 113 | w[i][0] = 0.5 * t * t; 114 | w[i][2] = 0.5 * (1.0 - t) * (1.0 - t); 115 | w[i][1] = 1.0 - w[i][0] - w[i][2]; 116 | } 117 | /* Evaluate noise by weighting noise coefficients by basis function values */ 118 | let mut result = 0.0; 119 | for fx in 0..3 { 120 | for fy in 0..3 { 121 | let cx = modulo(mid[0] + (fx as i32 - 1), n); 122 | let cy = modulo(mid[1] + (fy as i32 - 1), n); 123 | let weight = w[0][fx] * w[1][fy]; 124 | result += weight * noise_tile[cx + cy * n]; 125 | } 126 | } 127 | result 128 | } 129 | 130 | let noise_tile = generate_noise_tile(grid_resolution); 131 | 132 | let mut heights = Vec::new(); 133 | for x in 0..(grid_resolution * grid_spacing) { 134 | for y in 0..(grid_resolution * grid_spacing) { 135 | let p = [x as f32 / grid_spacing as f32, y as f32 / grid_spacing as f32]; 136 | heights.push(noise(&noise_tile, grid_resolution, p)) 137 | } 138 | } 139 | 140 | // Force mean = 0. 141 | let mean = heights.iter().sum::() / heights.len() as f32; 142 | for h in &mut heights { 143 | *h -= mean; 144 | } 145 | 146 | // Force variance = 1. 147 | let variance = heights.iter().map(|n| n * n).sum::() / heights.len() as f32; 148 | let inv_variance = 1.0 / variance; 149 | for h in &mut heights { 150 | *h *= inv_variance; 151 | } 152 | 153 | Heightmap { 154 | heights, 155 | width: (grid_resolution * grid_spacing) as u16, 156 | height: (grid_resolution * grid_spacing) as u16, 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /generate/src/sky/lut.rs: -------------------------------------------------------------------------------- 1 | use crate::asset::AssetLoadContext; 2 | use anyhow::Error; 3 | use rayon::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | pub(crate) trait LookupTableDefinition: Sync { 7 | fn name(&self) -> String; 8 | fn size(&self) -> [u16; 3]; 9 | fn compute(&self, _: [u16; 3]) -> [f32; 4]; 10 | 11 | fn inv_size(&self) -> [f64; 3] { 12 | let s = self.size(); 13 | [1.0 / f64::from(s[0]), 1.0 / f64::from(s[1]), 1.0 / f64::from(s[2])] 14 | } 15 | 16 | fn generate(&self, context: &mut AssetLoadContext) -> Result { 17 | let size = self.size(); 18 | let total = size[0] as u64 * size[1] as u64 * size[2] as u64; 19 | context.reset(format!("Generating {}... ", &self.name()), total / 1000); 20 | 21 | let data = (0..total) 22 | .into_iter() 23 | .collect::>() 24 | .chunks(1000) 25 | .enumerate() 26 | .flat_map(|(i, chunk)| { 27 | context.set_progress(i as u64); 28 | chunk 29 | .into_par_iter() 30 | .map(|i| { 31 | let x = i % size[0] as u64; 32 | let y = (i / size[0] as u64) % size[1] as u64; 33 | let z = i / (size[0] as u64 * size[1] as u64) % size[2] as u64; 34 | let value = self.compute([x as u16, y as u16, z as u16]); 35 | for c in &value { 36 | assert!(!c.is_nan()) 37 | } 38 | value 39 | }) 40 | .collect::>() 41 | }) 42 | .collect(); 43 | 44 | context.set_progress(total / 1000); 45 | Ok(LookupTable { size, data }) 46 | } 47 | } 48 | 49 | #[derive(Serialize, Deserialize, Clone)] 50 | pub(crate) struct LookupTable { 51 | pub size: [u16; 3], 52 | pub data: Vec<[f32; 4]>, 53 | } 54 | impl LookupTable { 55 | pub fn get2(&self, x: f64, y: f64) -> [f32; 4] { 56 | assert_eq!(self.size[2], 1); 57 | assert!(x >= 0.0); 58 | assert!(y >= 0.0); 59 | assert!(x <= 1.0); 60 | assert!(y <= 1.0); 61 | 62 | let x = (x * (self.size[0] - 1) as f64).round() as usize; 63 | let y = (y * (self.size[1] - 1) as f64).round() as usize; 64 | self.data[x + y * self.size[0] as usize] 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /generate/src/sky/precompute.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fintelia/terra/ba31d76c8e33d58f1327b6a128e2e9d870cc4933/generate/src/sky/precompute.rs -------------------------------------------------------------------------------- /generate/src/textures.rs: -------------------------------------------------------------------------------- 1 | use std::{io::Write, path::Path}; 2 | 3 | use anyhow::Error; 4 | use atomicwrites::{AtomicFile, OverwriteBehavior}; 5 | use image::GenericImageView; 6 | 7 | use crate::ktx2encode::encode_ktx2; 8 | use crate::sky::{InscatteringTable, LookupTableDefinition, TransmittanceTable}; 9 | 10 | fn image_to_ktx2(img: image::DynamicImage) -> Result, Error> { 11 | let (width, height) = img.dimensions(); 12 | let (img_data, format) = match img { 13 | image::DynamicImage::ImageRgb8(_) => { 14 | (img.to_rgba8().into_raw(), ktx2::Format::R8G8B8A8_UNORM) 15 | } 16 | image::DynamicImage::ImageRgba8(img) => (img.into_raw(), ktx2::Format::R8G8B8A8_UNORM), 17 | _ => todo!(), 18 | }; 19 | 20 | encode_ktx2(&[img_data], width, height, 0, 0, format) 21 | } 22 | 23 | pub fn generate_textures( 24 | base_directory: &Path, 25 | mut progress_callback: F, 26 | ) -> Result<(), Error> { 27 | let assets_directory = base_directory.join("serve").join("assets"); 28 | 29 | generate_materials(&base_directory, &mut progress_callback)?; 30 | generate_noise(&assets_directory, &mut progress_callback)?; 31 | generate_sky(&assets_directory, &mut progress_callback)?; 32 | 33 | let downloads = 34 | [("sky.ktx2", "https://www.eso.org/public/archives/images/original/eso0932a.tif")]; 35 | for (i, (filename, url)) in downloads.into_iter().enumerate() { 36 | progress_callback("Downloading textures".to_string(), i, downloads.len()); 37 | let filename = assets_directory.join(filename); 38 | if !filename.exists() { 39 | let response = reqwest::blocking::get(url)?; 40 | let contents = image_to_ktx2(image::load_from_memory(response.bytes()?.as_ref())?)?; 41 | AtomicFile::new(filename, OverwriteBehavior::AllowOverwrite) 42 | .write(|f| f.write_all(&contents))?; 43 | } 44 | } 45 | 46 | let convert = [ 47 | ("cloudcover.ktx2", "clouds_combined.png"), 48 | ("Oak_English_Sapling_Color.ktx2", "Oak_English_Sapling/Oak_English_Sapling_Color.png"), 49 | ("Oak_English_Sapling_Normal.ktx2", "Oak_English_Sapling/Oak_English_Sapling_Normal.png"), 50 | ("Oak_English_Sapling_SS.ktx2", "Oak_English_Sapling/Oak_English_Sapling_SS.png"), 51 | ]; 52 | for (i, (filename, original)) in convert.into_iter().enumerate() { 53 | progress_callback("Converting textures".to_string(), i, convert.len()); 54 | let filename = assets_directory.join(filename); 55 | if !filename.exists() { 56 | let contents = 57 | image_to_ktx2(image::open(base_directory.join("manual").join(original))?)?; 58 | AtomicFile::new(filename, OverwriteBehavior::AllowOverwrite) 59 | .write(|f| f.write_all(&contents))?; 60 | } 61 | } 62 | 63 | let copy = [("Oak_English_Sapling.xml.zip", "Oak_English_Sapling/Oak_English_Sapling.xml.zip")]; 64 | for (i, (filename, original)) in copy.into_iter().enumerate() { 65 | progress_callback("Copying assets".to_string(), i, copy.len()); 66 | let filename = assets_directory.join(filename); 67 | if !filename.exists() { 68 | let contents = std::fs::read(base_directory.join("manual").join(original))?; 69 | AtomicFile::new(filename, OverwriteBehavior::AllowOverwrite) 70 | .write(|f| f.write_all(&contents))?; 71 | } 72 | } 73 | 74 | Ok(()) 75 | } 76 | 77 | fn generate_materials( 78 | base_directory: &Path, 79 | mut progress_callback: F, 80 | ) -> Result<(), Error> { 81 | let filename = base_directory.join("serve").join("assets").join("ground_albedo.ktx2"); 82 | if filename.exists() { 83 | return Ok(()); 84 | } 85 | 86 | let materials = [("ground", "leafy-grass2"), ("ground", "grass1"), ("rocks", "granite5")]; 87 | 88 | let mut albedo_data = vec![Vec::new(); 11]; 89 | for (i, (group, name)) in materials.iter().enumerate() { 90 | progress_callback("Generating materials".to_string(), i, materials.len()); 91 | 92 | let mut albedo_path = None; 93 | for file in std::fs::read_dir( 94 | &base_directory 95 | .join("manual") 96 | .join("free_pbr") 97 | .join(format!("Blender/{}-bl/{}-bl", group, name)), 98 | )? { 99 | let file = file?; 100 | let filename = file.file_name(); 101 | let filename = filename.to_string_lossy(); 102 | if filename.contains("albedo") { 103 | albedo_path = Some(file.path()); 104 | } 105 | } 106 | 107 | let mut albedo = image::open(albedo_path.unwrap())?.to_rgba8(); 108 | //material::high_pass_filter(&mut albedo); 109 | assert_eq!(albedo.width(), 2048); 110 | assert_eq!(albedo.height(), 2048); 111 | 112 | for mip in 0..=10 { 113 | albedo = image::imageops::resize( 114 | &albedo, 115 | 1024 >> mip, 116 | 1024 >> mip, 117 | image::imageops::FilterType::Triangle, 118 | ); 119 | 120 | albedo_data[mip].extend_from_slice(albedo.as_raw()); 121 | } 122 | } 123 | 124 | let contents = encode_ktx2( 125 | &*albedo_data, 126 | 1024, 127 | 1024, 128 | 0, 129 | materials.len() as u32, 130 | ktx2::Format::R8G8B8A8_UNORM, 131 | )?; 132 | Ok(AtomicFile::new(filename, OverwriteBehavior::AllowOverwrite) 133 | .write(|f| f.write_all(&contents))?) 134 | } 135 | 136 | fn generate_noise( 137 | assets_directory: &Path, 138 | mut progress_callback: F, 139 | ) -> Result<(), Error> { 140 | let filename = assets_directory.join("noise.ktx2"); 141 | if filename.exists() { 142 | return Ok(()); 143 | } 144 | 145 | // wavelength = 1.0 / 256.0; 146 | let noise_heightmaps: Vec<_> = 147 | (0..4).map(|i| crate::noise::wavelet_noise(64 << i, 32 >> i)).collect(); 148 | 149 | let len = noise_heightmaps[0].heights.len(); 150 | let mut contents = vec![0u8; len * 4]; 151 | for (i, heightmap) in noise_heightmaps.into_iter().enumerate() { 152 | progress_callback("Generating noise textures".to_string(), i, 4); 153 | let mut dist: Vec<(usize, f32)> = heightmap.heights.into_iter().enumerate().collect(); 154 | dist.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap()); 155 | for j in 0..len { 156 | contents[dist[j].0 * 4 + i] = (j * 256 / len) as u8; 157 | } 158 | } 159 | 160 | progress_callback("Saving noise textures".to_string(), 0, 1); 161 | let contents = encode_ktx2(&[contents], 2048, 2048, 0, 0, ktx2::Format::R8G8B8A8_UNORM)?; 162 | Ok(AtomicFile::new(filename, OverwriteBehavior::AllowOverwrite) 163 | .write(|f| f.write_all(&contents))?) 164 | } 165 | 166 | fn generate_sky( 167 | assets_directory: &Path, 168 | mut progress_callback: F, 169 | ) -> Result<(), Error> { 170 | let filename = assets_directory.join("transmittance.ktx2"); 171 | if !filename.exists() { 172 | let transmittance = TransmittanceTable { steps: 1000 }.generate(&mut progress_callback)?; 173 | 174 | let contents = encode_ktx2( 175 | &[bytemuck::cast_slice(&transmittance.data).to_vec()], 176 | transmittance.size[0] as u32, 177 | transmittance.size[1] as u32, 178 | 0, 179 | 0, 180 | ktx2::Format::R32G32B32A32_SFLOAT, 181 | )?; 182 | AtomicFile::new(filename, OverwriteBehavior::AllowOverwrite) 183 | .write(|f| f.write_all(&contents))?; 184 | } 185 | 186 | let filename = assets_directory.join("inscattering.ktx2"); 187 | if !filename.exists() { 188 | let transmittance = TransmittanceTable { steps: 1000 }.generate(&mut progress_callback)?; 189 | let inscattering = InscatteringTable { steps: 30, transmittance: &transmittance } 190 | .generate(&mut progress_callback)?; 191 | 192 | let contents = encode_ktx2( 193 | &[bytemuck::cast_slice(&inscattering.data).to_vec()], 194 | inscattering.size[0] as u32, 195 | inscattering.size[1] as u32, 196 | inscattering.size[2] as u32, 197 | 0, 198 | ktx2::Format::R32G32B32A32_SFLOAT, 199 | )?; 200 | AtomicFile::new(filename, OverwriteBehavior::AllowOverwrite) 201 | .write(|f| f.write_all(&contents))?; 202 | } 203 | 204 | Ok(()) 205 | } 206 | -------------------------------------------------------------------------------- /planetcam/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "planetcam" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | cgmath = { version = "0.18.0", git = "https://github.com/rustgd/cgmath", rev = "d5e765db61cf9039cb625a789a59ddf6b6ab2337" } 10 | geo = "0.29.3" 11 | mint = "0.5.9" 12 | 13 | [dev-dependencies] 14 | approx = "0.5.1" 15 | -------------------------------------------------------------------------------- /planetcam/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::f64::consts::PI; 2 | 3 | use geo::prelude::*; 4 | use mint::{ColumnMatrix3, ColumnMatrix4, Vector3}; 5 | 6 | #[derive(Clone, Debug)] 7 | struct PlanetCam { 8 | latitude: f64, 9 | longitude: f64, 10 | bearing: f64, 11 | pitch: f64, 12 | height: f64, 13 | } 14 | 15 | impl PlanetCam { 16 | fn move_forward(&mut self, meters: f64) { 17 | if meters == 0.0 { 18 | return; 19 | } 20 | 21 | let start = geo::Point::new(self.longitude, self.latitude); 22 | let end = start.haversine_destination(self.bearing, meters); 23 | let new_bearing = if meters > 0.0 { 24 | end.haversine_bearing(start) + 180.0 25 | } else { 26 | end.haversine_bearing(start) 27 | }; 28 | 29 | assert_eq!(start.y(), self.latitude); 30 | assert_eq!(start.x(), self.longitude); 31 | 32 | self.latitude = end.y(); 33 | self.longitude = end.x(); 34 | self.bearing = new_bearing; 35 | } 36 | fn move_right(&mut self, meters: f64) { 37 | if meters == 0.0 { 38 | return; 39 | } 40 | 41 | let start = geo::Point::new(self.longitude, self.latitude); 42 | let end = start.haversine_destination(self.bearing + 90.0, meters); 43 | let new_bearing = if meters > 0.0 { 44 | end.haversine_bearing(start) + 90.0 45 | } else { 46 | end.haversine_bearing(start) - 90.0 47 | }; 48 | 49 | self.latitude = end.y().min(89.999).max(-89.999); 50 | self.longitude = end.x(); 51 | self.bearing = new_bearing; 52 | } 53 | fn move_up(&mut self, meters: f64) { 54 | self.height = (self.height + meters).max(0.001); 55 | } 56 | 57 | fn increase_bearing(&mut self, degrees: f64) { 58 | self.bearing += degrees; 59 | if self.bearing >= 360.0 { 60 | self.bearing -= 360.0; 61 | } 62 | if self.bearing < 0.0 { 63 | self.bearing += 360.0; 64 | } 65 | } 66 | fn increase_pitch(&mut self, degrees: f64) { 67 | self.pitch += degrees; 68 | self.pitch = self.pitch.min(89.0).max(-89.0); 69 | } 70 | 71 | /// Returns the ECEF position and the view matrix associated with this camera. 72 | fn position_view(&self, terrain_elevation: f64) -> (Vector3, ColumnMatrix3) { 73 | let r = 6371000.0 + self.height + terrain_elevation; 74 | let lat = self.latitude.to_radians(); 75 | let long = self.longitude.to_radians(); 76 | 77 | const A: f64 = 6378137.0; 78 | const B: f64 = 6356752.314245; 79 | 80 | let up = cgmath::Vector3::new(lat.cos() * long.cos(), lat.cos() * long.sin(), lat.sin()); 81 | 82 | let n = A.powi(2) / (A.powi(2) * lat.cos().powi(2) + B.powi(2) * lat.sin().powi(2)).sqrt(); 83 | let position = cgmath::Vector3::new( 84 | (n + self.height + terrain_elevation) * lat.cos() * long.cos(), 85 | (n + self.height + terrain_elevation) * lat.cos() * long.sin(), 86 | (n * (B / A).powi(2) + self.height + terrain_elevation) * lat.sin(), 87 | ); 88 | 89 | let adjusted_pitch = 90 | (self.pitch.to_radians() - f64::acos(6371000.0 / r)).clamp(-0.499 * PI, 0.499 * PI); 91 | 92 | let start = geo::Point::new(self.longitude, self.latitude); 93 | let center = start.haversine_destination(self.bearing, 1.0); 94 | let latc = center.y().to_radians(); 95 | let longc = center.x().to_radians(); 96 | let forward = (1.0 + adjusted_pitch.tan() / 6371000.0) 97 | * cgmath::Vector3::new(latc.cos() * longc.cos(), latc.cos() * longc.sin(), latc.sin()) 98 | - up; 99 | 100 | let matrix = cgmath::Matrix3::look_to_rh(forward, up); 101 | (position.into(), matrix.cast().unwrap().into()) 102 | } 103 | } 104 | 105 | pub struct DualPlanetCam { 106 | anchored: Option, 107 | free: PlanetCam, 108 | } 109 | impl DualPlanetCam { 110 | pub fn new(latitude: f64, longitude: f64, bearing: f64, pitch: f64, height: f64) -> Self { 111 | Self { anchored: None, free: PlanetCam { latitude, longitude, bearing, pitch, height } } 112 | } 113 | 114 | pub fn detach(&mut self) { 115 | self.anchored = Some(self.free.clone()); 116 | } 117 | pub fn attach(&mut self) { 118 | self.anchored = None; 119 | } 120 | pub fn is_detached(&self) -> bool { 121 | self.anchored.is_some() 122 | } 123 | 124 | pub fn move_forward(&mut self, meters: f64) { 125 | self.free.move_forward(meters); 126 | } 127 | pub fn move_right(&mut self, meters: f64) { 128 | self.free.move_right(meters); 129 | } 130 | pub fn move_up(&mut self, meters: f64) { 131 | self.free.move_up(meters); 132 | } 133 | 134 | pub fn increase_bearing(&mut self, degrees: f64) { 135 | self.free.increase_bearing(degrees); 136 | } 137 | pub fn increase_pitch(&mut self, degrees: f64) { 138 | self.free.increase_pitch(degrees); 139 | } 140 | 141 | pub fn latitude_longitude(&self) -> (f64, f64) { 142 | (self.free.latitude, self.free.longitude) 143 | } 144 | pub fn height(&self) -> f64 { 145 | self.free.height 146 | } 147 | 148 | pub fn anchored_latitude_longitude(&self) -> (f64, f64) { 149 | let c = self.anchored.as_ref().unwrap_or(&self.free); 150 | (c.latitude, c.longitude) 151 | } 152 | 153 | pub fn anchored_position_view( 154 | &self, 155 | terrain_elevation: f64, 156 | ) -> (Vector3, ColumnMatrix3) { 157 | match &self.anchored { 158 | Some(a) => a.position_view(terrain_elevation), 159 | None => self.free.position_view(terrain_elevation), 160 | } 161 | } 162 | pub fn free_position_view(&self, terrain_elevation: f64) -> ColumnMatrix4 { 163 | match &self.anchored { 164 | Some(a) => { 165 | let anchored = a.position_view(terrain_elevation); 166 | let free = self.free.position_view(terrain_elevation); 167 | 168 | let look_at = cgmath::Matrix4::from(cgmath::Matrix3::from(free.1)); 169 | let translation = cgmath::Vector3::from(anchored.0) - cgmath::Vector3::from(free.0); 170 | 171 | (look_at * cgmath::Matrix4::from_translation(translation.cast().unwrap())).into() 172 | } 173 | None => cgmath::Matrix4::from(cgmath::Matrix3::from( 174 | self.free.position_view(terrain_elevation).1, 175 | )) 176 | .into(), 177 | } 178 | } 179 | } 180 | 181 | #[cfg(test)] 182 | mod tests { 183 | use cgmath::{assert_abs_diff_eq, MetricSpace}; 184 | use geo::prelude::HaversineDestination; 185 | 186 | use crate::PlanetCam; 187 | 188 | #[test] 189 | fn it_works() { 190 | let camera = 191 | PlanetCam { latitude: 23.0, longitude: 45.0, height: 100.0, bearing: 67.0, pitch: 0.0 }; 192 | let mut camera2 = camera.clone(); 193 | for x in 1..100 { 194 | let mut full = camera.clone(); 195 | full.move_forward(10000.0 * x as f64); 196 | 197 | camera2.move_forward(10000.0); 198 | 199 | assert_abs_diff_eq!(full.latitude, camera2.latitude, epsilon = 0.0001); 200 | assert_abs_diff_eq!(full.longitude, camera2.longitude, epsilon = 0.0001); 201 | } 202 | } 203 | 204 | #[test] 205 | fn move_distance() { 206 | let camera = 207 | PlanetCam { latitude: 23.0, longitude: 45.0, height: 100.0, bearing: 67.0, pitch: 0.0 }; 208 | let mut camera2 = camera.clone(); 209 | camera2.move_forward(100.0); 210 | 211 | const MEAN_EARTH_RADIUS: f64 = 6371008.8; 212 | const MEAN_EARTH_CIRCUMFERENCE: f64 = MEAN_EARTH_RADIUS * std::f64::consts::PI; 213 | 214 | let (lat, long) = (camera.latitude.to_radians(), camera.longitude.to_radians()); 215 | let position = MEAN_EARTH_RADIUS 216 | * cgmath::Vector3::new(lat.cos() * long.cos(), lat.cos() * long.sin(), lat.sin()); 217 | 218 | let (lat, long) = (camera2.latitude.to_radians(), camera2.longitude.to_radians()); 219 | let position2 = MEAN_EARTH_RADIUS 220 | * cgmath::Vector3::new(lat.cos() * long.cos(), lat.cos() * long.sin(), lat.sin()); 221 | 222 | let distance = position.distance(position2); 223 | assert_abs_diff_eq!(distance, 100.0, epsilon = 0.1); 224 | 225 | let start = geo::Point::new(camera.longitude, 0.0); 226 | let end = start.haversine_destination(0.0, 1000.0); 227 | assert_abs_diff_eq!( 228 | end.y() - start.y(), 229 | 1000.0 * 180.0 / MEAN_EARTH_CIRCUMFERENCE, 230 | epsilon = 0.0000001 231 | ); 232 | 233 | let end = start.haversine_destination(90.0, 1000.0); 234 | assert_abs_diff_eq!( 235 | end.x() - start.x(), 236 | 1000.0 * 180.0 / MEAN_EARTH_CIRCUMFERENCE, 237 | epsilon = 0.0000001 238 | ); 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /preview/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "terra-preview" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | cgmath = { version = "0.18.0", features = ["mint"], git = "https://github.com/rustgd/cgmath", rev = "d5e765db61cf9039cb625a789a59ddf6b6ab2337" } 8 | clap = { version = "4.1.11", features = ["derive"] } 9 | env_logger = "0.10.0" 10 | gilrs = "0.10.1" 11 | indicatif = "0.17.3" 12 | mint = "0.5.9" 13 | open-location-code = "0.1.0" 14 | planetcam = { path = "../planetcam" } 15 | smaa = { version = "0.9.0", optional = true } 16 | terra = { path = "..", default-features = false } 17 | tokio = { version = "1.26.0", features = ["fs", "macros", "sync", "rt", "rt-multi-thread", "io-util"] } 18 | wgpu = "0.15.1" 19 | winit = {version = "0.28.3", default-features = false, features = ["x11", "wayland", "wayland-dlopen"] } 20 | terra-generate = { path = "../generate", optional = true } 21 | time = { version = "0.3.20", features = ["parsing", "macros"] } 22 | 23 | [features] 24 | default = [] 25 | generate = ["terra-generate"] 26 | -------------------------------------------------------------------------------- /rshader/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rshader" 3 | version = "0.2.0" 4 | authors = ["Jonathan Behrens "] 5 | license = "Apache-2.0" 6 | description = "A simply library for reloading shaders at runtime" 7 | repository = "https://github.com/fintelia/terra" 8 | homepage = "https://github.com/fintelia/terra" 9 | categories = ["game-engines", "rendering"] 10 | edition = "2021" 11 | 12 | [dependencies] 13 | anyhow = "1.0.70" 14 | bytemuck = "1.13.1" 15 | lazy_static = "1.4.0" 16 | naga = { version = "0.11.0", features = ["glsl-in", "wgsl-in", "span"] } 17 | notify = "5.1.0" 18 | wgpu = { version = "0.15.1", features = ["naga"] } 19 | 20 | [features] 21 | default = [] 22 | dynamic_shaders = [] 23 | -------------------------------------------------------------------------------- /rshader/src/dynamic_shaders.rs: -------------------------------------------------------------------------------- 1 | use std::time::Instant; 2 | 3 | use super::{ShaderSetInner, ShaderSource}; 4 | 5 | -------------------------------------------------------------------------------- /rshader/src/static_shaders.rs: -------------------------------------------------------------------------------- 1 | use notify; 2 | use std::path::PathBuf; 3 | 4 | use super::*; 5 | 6 | pub struct ShaderDirectoryWatcher {} 7 | impl ShaderDirectoryWatcher { 8 | pub fn new

(_: P) -> Result 9 | where 10 | PathBuf: From

, 11 | { 12 | Ok(Self {}) 13 | } 14 | } 15 | 16 | pub struct ShaderSet { 17 | inner: ShaderSetInner, 18 | } 19 | impl ShaderSet { 20 | pub fn simple( 21 | _: &mut ShaderDirectoryWatcher, 22 | vertex_source: ShaderSource, 23 | fragment_source: ShaderSource, 24 | ) -> Result { 25 | Ok(Self { 26 | inner: ShaderSetInner::simple( 27 | vertex_source.source.unwrap(), 28 | fragment_source.source.unwrap(), 29 | )?, 30 | }) 31 | } 32 | pub fn compute_only( 33 | _: &mut ShaderDirectoryWatcher, 34 | compute_source: ShaderSource, 35 | ) -> Result { 36 | Ok(Self { inner: ShaderSetInner::compute_only(compute_source.source.unwrap())? }) 37 | } 38 | pub fn refresh(&mut self, _: &mut ShaderDirectoryWatcher) -> bool { 39 | false 40 | } 41 | 42 | pub fn layout_descriptor(&self) -> wgpu::BindGroupLayoutDescriptor { 43 | wgpu::BindGroupLayoutDescriptor { 44 | entries: self.inner.layout_descriptor[..].into(), 45 | label: None, 46 | } 47 | } 48 | pub fn desc_names(&self) -> &[Option] { 49 | &self.inner.desc_names[..] 50 | } 51 | pub fn input_attributes(&self) -> &[wgpu::VertexAttributeDescriptor] { 52 | &self.inner.input_attributes[..] 53 | } 54 | 55 | pub fn vertex(&self) -> &[u32] { 56 | self.inner.vertex.as_ref().unwrap() 57 | } 58 | pub fn fragment(&self) -> &[u32] { 59 | self.inner.fragment.as_ref().unwrap() 60 | } 61 | pub fn compute(&self) -> &[u32] { 62 | self.inner.compute.as_ref().unwrap() 63 | } 64 | pub fn digest(&self) -> &[u8] { 65 | &self.inner.digest 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | use_small_heuristics = "max" -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fintelia/terra/ba31d76c8e33d58f1327b6a128e2e9d870cc4933/screenshot.png -------------------------------------------------------------------------------- /src/astro.rs: -------------------------------------------------------------------------------- 1 | //! Astronomy functions 2 | //! 3 | //! The methods in this module are derived from the astro crate. 4 | //! The original crate is available at https://github.com/saurvs/astro-rust 5 | //! 6 | //! ORIGINAL LICENSE NOTICE: 7 | //! 8 | //! Copyright (c) 2015, 2016 Saurav Sachidanand 9 | //! 10 | //! Permission is hereby granted, free of charge, to any person obtaining a copy of this 11 | //! software and associated documentation files (the "Software"), to deal in the Software 12 | //! without restriction, including without limitation the rights to use, copy, modify, merge, 13 | //! publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 14 | //! to whom the Software is furnished to do so, subject to the following conditions: 15 | //! 16 | //! The above copyright notice and this permission notice shall be included in all copies or 17 | //! substantial portions of the Software. 18 | //! 19 | //! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 20 | //! INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 21 | //! PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 22 | //! FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 23 | //! OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | //! DEALINGS IN THE SOFTWARE. 25 | 26 | /// Computes Julian century for a Julian day 27 | /// 28 | /// # Arguments 29 | /// 30 | /// * `JD`: Julian (Ephemeris) day 31 | #[inline] 32 | pub(crate) fn julian_cent(jd: f64) -> f64 { 33 | (jd - 2451545.0) / 36525.0 34 | } 35 | 36 | /// Computes mean sidereal time for a Julian day 37 | /// 38 | /// # Returns 39 | /// 40 | /// * `mn_sidr`: Mean sidereal time *| in radians* 41 | /// 42 | /// # Arguments 43 | /// 44 | /// * `JD`: Julian day 45 | pub(crate) fn mn_sidr(jd: f64) -> f64 { 46 | let jc = julian_cent(jd); 47 | 48 | limit_to_360( 49 | 280.46061837 50 | + 360.98564736629 * (jd - 2451545.0) 51 | + jc * jc * (0.000387933 - jc / 38710000.0), 52 | ) 53 | .to_radians() 54 | } 55 | 56 | /// Computes the equivalent angle in [0, 360] degree range 57 | /// 58 | /// # Arguments 59 | /// 60 | /// * `angl`: Angle *| in degrees* 61 | #[inline] 62 | fn limit_to_360(angl: f64) -> f64 { 63 | let n = (angl / 360.0) as i64; 64 | let limited_angl = angl - (360.0 * (n as f64)); 65 | 66 | if limited_angl < 0.0 { 67 | limited_angl + 360.0 68 | } else { 69 | limited_angl 70 | } 71 | } 72 | 73 | /// Computes the right ascension from ecliptic coordinates 74 | /// 75 | /// # Returns 76 | /// 77 | /// * `asc`: Right ascension *| in radians* 78 | /// 79 | /// # Arguments 80 | /// 81 | /// * `ecl_long`: Ecliptic longitude *| in radians* 82 | /// * `ecl_lat`: Ecliptic latitude *| in radians* 83 | /// * `oblq_eclip`: If `ecl_long` and `ecl_lat` are corrected 84 | /// for nutation, then *true* obliquity. If not, then 85 | /// *mean* obliquity. *| in radians* 86 | pub(crate) fn asc_frm_ecl(ecl_long: f64, ecl_lat: f64, oblq_eclip: f64) -> f64 { 87 | (ecl_long.sin() * oblq_eclip.cos() - ecl_lat.tan() * oblq_eclip.sin()).atan2(ecl_long.cos()) 88 | } 89 | 90 | /// Computes the declination from ecliptic coordinates 91 | /// 92 | /// # Returns 93 | /// 94 | /// * `dec`: Declination *| in radians* 95 | /// 96 | /// # Arguments 97 | /// 98 | /// * `ecl_long`: Ecliptic longitude *| in radians* 99 | /// * `ecl_lat`: Ecliptic latitude *| in radians* 100 | /// * `oblq_eclip`: If `ecl_long` and `ecl_lat` are corrected 101 | /// for nutation, then *true* obliquity. If not, then 102 | /// *mean* obliquity. *| in radians* 103 | pub(crate) fn dec_frm_ecl(ecl_long: f64, ecl_lat: f64, oblq_eclip: f64) -> f64 { 104 | (ecl_lat.sin() * oblq_eclip.cos() + ecl_lat.cos() * oblq_eclip.sin() * ecl_long.sin()).asin() 105 | } 106 | 107 | /// Computes the right ascension from galactic coordinates 108 | /// 109 | /// # Returns 110 | /// 111 | /// * `asc`: Right ascension *| in radians* 112 | /// 113 | /// The right ascension returned here is referred to the standard equinox 114 | /// of B1950.0. 115 | /// 116 | /// # Arguments 117 | /// 118 | /// * `gal_long`: Galactic longitude *| in radians* 119 | /// * `gal_lat`: Galactic latitude *| in radians* 120 | pub(crate) fn asc_frm_gal(gal_long: f64, gal_lat: f64) -> f64 { 121 | 12.25_f64.to_radians() 122 | + (gal_long - 123_f64.to_radians()).sin().atan2( 123 | 27.4_f64.to_radians().sin() * (gal_long - 123_f64.to_radians()).cos() 124 | - 27.4_f64.to_radians().cos() * gal_lat.tan(), 125 | ) 126 | } 127 | 128 | /// Computes the declination from galactic coordinates 129 | /// 130 | /// # Returns 131 | /// 132 | /// * `dec`: Declination *| in radians* 133 | /// 134 | /// The declination returned here is referred to the standard equinox 135 | /// of B1950.0. 136 | /// 137 | /// # Arguments 138 | /// 139 | /// * `gal_long`: Galactic longitude *| in radians* 140 | /// * `gal_lat`: Galactic latitude *| in radians* 141 | pub(crate) fn dec_frm_gal(gal_long: f64, gal_lat: f64) -> f64 { 142 | (gal_lat.sin() * 27.4_f64.to_radians().sin() 143 | + gal_lat.cos() * 27.4_f64.to_radians().cos() * (gal_long - 123_f64.to_radians()).cos()) 144 | .asin() 145 | } 146 | -------------------------------------------------------------------------------- /src/compute_shader.rs: -------------------------------------------------------------------------------- 1 | use maplit::hashmap; 2 | 3 | use crate::GpuState; 4 | use std::{collections::HashMap, mem}; 5 | 6 | pub(crate) struct ComputeShader { 7 | shader: rshader::ShaderSet, 8 | bindgroup_pipeline: Option<(wgpu::BindGroup, wgpu::ComputePipeline)>, 9 | uniforms: Option, 10 | name: String, 11 | _phantom: std::marker::PhantomData, 12 | } 13 | impl ComputeShader { 14 | pub fn new(shader: rshader::ShaderSource, name: String) -> Self { 15 | Self { 16 | shader: rshader::ShaderSet::compute_only(shader).unwrap(), 17 | bindgroup_pipeline: None, 18 | uniforms: None, 19 | name, 20 | _phantom: std::marker::PhantomData, 21 | } 22 | } 23 | 24 | pub fn refresh(&mut self, device: &wgpu::Device, gpu_state: &GpuState) -> bool { 25 | if mem::size_of::() > 0 && self.uniforms.is_none() { 26 | self.uniforms = Some(device.create_buffer(&wgpu::BufferDescriptor { 27 | size: mem::size_of::() as u64, 28 | usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, 29 | mapped_at_creation: false, 30 | label: Some(&format!("buffer.{}.uniforms", &self.name)), 31 | })); 32 | } 33 | 34 | let refreshed = self.shader.refresh(); 35 | 36 | if refreshed || self.bindgroup_pipeline.is_none() { 37 | let (bind_group, bind_group_layout) = gpu_state.bind_group_for_shader( 38 | device, 39 | &self.shader, 40 | if self.uniforms.is_some() { 41 | hashmap!["ubo".into() => (false, wgpu::BindingResource::Buffer(wgpu::BufferBinding { 42 | buffer: self.uniforms.as_ref().unwrap(), 43 | offset: 0, 44 | size: None, 45 | }))] 46 | } else { 47 | HashMap::new() 48 | }, 49 | HashMap::new(), 50 | &format!("bindgroup.{}", self.name), 51 | ); 52 | self.bindgroup_pipeline = Some(( 53 | bind_group, 54 | device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { 55 | layout: Some(&device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { 56 | bind_group_layouts: [&bind_group_layout][..].into(), 57 | push_constant_ranges: &[], 58 | label: Some(&format!("pipeline.{}.layout", self.name)), 59 | })), 60 | module: &device.create_shader_module(wgpu::ShaderModuleDescriptor { 61 | label: Some(&format!("shader.{}", self.name)), 62 | source: self.shader.compute(), 63 | }), 64 | entry_point: "main", 65 | label: Some(&format!("pipeline.{}", self.name)), 66 | }), 67 | )) 68 | } 69 | 70 | refreshed 71 | } 72 | 73 | pub fn run( 74 | &self, 75 | device: &wgpu::Device, 76 | encoder: &mut wgpu::CommandEncoder, 77 | _state: &GpuState, 78 | dimensions: (u32, u32, u32), 79 | uniforms: &U, 80 | ) { 81 | if self.uniforms.is_some() { 82 | let staging = device.create_buffer(&wgpu::BufferDescriptor { 83 | size: mem::size_of::() as u64, 84 | usage: wgpu::BufferUsages::COPY_SRC, 85 | label: Some(&format!("buffer.temporary.{}.upload", self.name)), 86 | mapped_at_creation: true, 87 | }); 88 | let mut buffer_view = staging.slice(..).get_mapped_range_mut(); 89 | buffer_view[..mem::size_of::()].copy_from_slice(bytemuck::bytes_of(uniforms)); 90 | drop(buffer_view); 91 | staging.unmap(); 92 | 93 | encoder.copy_buffer_to_buffer( 94 | &staging, 95 | 0, 96 | self.uniforms.as_ref().unwrap(), 97 | 0, 98 | mem::size_of::() as u64, 99 | ); 100 | } 101 | 102 | let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None }); 103 | cpass.set_pipeline(&self.bindgroup_pipeline.as_ref().unwrap().1); 104 | cpass.set_bind_group(0, &self.bindgroup_pipeline.as_ref().unwrap().0, &[]); 105 | cpass.dispatch_workgroups(dimensions.0, dimensions.1, dimensions.2); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/mapfile.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Error; 2 | use atomicwrites::{AtomicFile, OverwriteBehavior}; 3 | use std::collections::HashSet; 4 | use std::fs; 5 | use std::io::{Cursor, Write}; 6 | use std::path::PathBuf; 7 | use std::str::FromStr; 8 | use std::sync::{Arc, Mutex}; 9 | use terra_types::VNode; 10 | 11 | lazy_static! { 12 | static ref TERRA_DIRECTORY: PathBuf = 13 | dirs::cache_dir().unwrap_or(PathBuf::from(".")).join("terra"); 14 | } 15 | 16 | pub(crate) struct MapFile { 17 | server: String, 18 | remote_tiles: Arc>>, 19 | } 20 | impl MapFile { 21 | pub(crate) async fn new(server: String) -> Result { 22 | // Create cache directory if necessary. 23 | fs::create_dir_all(&*TERRA_DIRECTORY)?; 24 | 25 | // Download file list if necessary. 26 | let file_list_path = TERRA_DIRECTORY.join("tile_list.txt.zstd"); 27 | let file_list_encoded = if !file_list_path.exists() { 28 | let contents = Self::download(&server, "tile_list.txt.zstd").await?; 29 | if server.starts_with("http://") || server.starts_with("https://") { 30 | tokio::fs::write(&file_list_path, &contents).await?; 31 | } 32 | contents 33 | } else { 34 | tokio::fs::read(file_list_path).await? 35 | }; 36 | 37 | // Parse file list to learn all files available from the remote. 38 | let remote_files = String::from_utf8(zstd::decode_all(Cursor::new(&file_list_encoded))?)?; 39 | let remote_tiles = remote_files 40 | .split('\n') 41 | .filter_map(|f| f.strip_suffix(".zip")) 42 | .map(VNode::from_str) 43 | .collect::, Error>>()?; 44 | 45 | Ok(Self { server, remote_tiles: Arc::new(Mutex::new(remote_tiles)) }) 46 | } 47 | 48 | pub(crate) async fn read_tile(&self, node: VNode) -> Result>, Error> { 49 | let filename = TERRA_DIRECTORY.join("tiles").join(&format!("{}.zip", node)); 50 | if filename.exists() { 51 | Ok(Some(tokio::fs::read(&filename).await?)) 52 | } else { 53 | if !self.remote_tiles.lock().unwrap().contains(&node) { 54 | return Ok(None); 55 | } 56 | let contents = Self::download(&self.server, &format!("tiles/{}.zip", node)).await?; 57 | if self.server.starts_with("http://") || self.server.starts_with("https://") { 58 | if let Some(parent) = filename.parent() { 59 | fs::create_dir_all(parent)?; 60 | } 61 | AtomicFile::new(filename, OverwriteBehavior::AllowOverwrite) 62 | .write(|f| f.write_all(&contents))?; 63 | } 64 | Ok(Some(contents)) 65 | } 66 | } 67 | 68 | pub(crate) async fn read_asset(&self, name: &str) -> Result, Error> { 69 | let filename = TERRA_DIRECTORY.join("assets").join(name); 70 | if filename.exists() { 71 | Ok(tokio::fs::read(&filename).await?) 72 | } else { 73 | let contents = Self::download(&self.server, &format!("assets/{}", name)).await?; 74 | if self.server.starts_with("http://") || self.server.starts_with("https://") { 75 | if let Some(parent) = filename.parent() { 76 | fs::create_dir_all(parent)?; 77 | } 78 | AtomicFile::new(filename, OverwriteBehavior::AllowOverwrite) 79 | .write(|f| f.write_all(&contents))?; 80 | } 81 | Ok(contents) 82 | } 83 | } 84 | 85 | async fn download(server: &str, path: &str) -> Result, Error> { 86 | match server.split_once("//") { 87 | Some(("file:", base_path)) => { 88 | let full_path = PathBuf::from(base_path).join(path); 89 | Ok(tokio::fs::read(&full_path).await?) 90 | } 91 | Some(("http:", ..)) | Some(("https:", ..)) => { 92 | let url = format!("{}{}", server, path); 93 | let client = hyper::Client::builder() 94 | .build::<_, hyper::Body>(hyper_tls::HttpsConnector::new()); 95 | let resp = client.get(url.parse()?).await?; 96 | if resp.status().is_success() { 97 | Ok(hyper::body::to_bytes(resp.into_body()).await?.to_vec()) 98 | } else { 99 | Err(anyhow::format_err!( 100 | "Tile download failed with {:?} for URL '{}'", 101 | resp.status(), 102 | url 103 | )) 104 | } 105 | } 106 | _ => Err(anyhow::format_err!("Invalid server URL {}", server)), 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/shaders/atmosphere.glsl: -------------------------------------------------------------------------------- 1 | 2 | const float planetRadius = 6378137.0; 3 | const float atmosphereRadius = 6378137.0 + 100000.0; 4 | 5 | const vec3 rayleigh_Bs = vec3(5.8e-6, 13.5e-6, 33.1e-6); 6 | 7 | float rayleigh_phase(float mu) { 8 | return 3.0 / (16.0 * 3.141592) * (1.0 + mu * mu); 9 | // return 0.8 * (1.4 + 0.5 * mu) / 10; 10 | } 11 | float mie_phase(float mu) { 12 | float g = 0.76; 13 | 14 | return 3.0 / (8.0 * 3.141592) * ((1.0 - g * g) * (1.0 + mu * mu)) 15 | / ((2.0 + g * g) * pow(1.0 + g * g - 2.0 * g * mu, 1.5)); 16 | } 17 | 18 | vec3 precomputed_transmittance(float r, float mu); 19 | 20 | // Ray-sphere intersection that assumes the sphere is centered at the origin. 21 | // No intersection when result.x > result.y 22 | vec2 rsi(vec3 r0, vec3 rd, float sr) { 23 | float a = dot(rd, rd); 24 | float b = 2.0 * dot(rd, r0); 25 | float c = dot(r0, r0) - (sr * sr); 26 | float d = (b*b) - 4.0*a*c; 27 | if (d < 0.0) return vec2(1e5,-1e5); 28 | return vec2( 29 | (-b - sqrt(d))/(2.0*a), 30 | (-b + sqrt(d))/(2.0*a) 31 | ); 32 | } 33 | 34 | vec3 atmosphere(vec3 r0, vec3 r1, vec3 pSun) { 35 | float iSun = 100000.0; 36 | vec3 kRlh = vec3(5.8e-6, 13.5e-6, 33.1e-6); 37 | float kMie = 2.0e-6; 38 | float shRlh = 8000.0; 39 | float shMie = 1200.0; 40 | float g = 0.76; 41 | 42 | const int iSteps = 32; 43 | 44 | // Normalize the sun and view directions. 45 | vec3 r = normalize(r1 - r0); 46 | 47 | // Primary ray initialization. 48 | float iStepSize = distance(r0, r1) / float(iSteps); 49 | float iTime = iStepSize * 0.5; 50 | 51 | // Initialize accumulators for Rayleigh and Mie scattering. 52 | vec3 totalRlh = vec3(0,0,0); 53 | vec3 totalMie = vec3(0,0,0); 54 | 55 | // Initialize optical depth accumulators for the primary ray. 56 | float iOdRlh = 0.0; 57 | float iOdMie = 0.0; 58 | 59 | // Sample the primary ray. 60 | for (int i = 0; i < iSteps; i++) { 61 | // Calculate the primary ray sample position. 62 | vec3 iPos = r0 + r * iTime; 63 | 64 | // Calculate the height of the sample. 65 | float iHeight = length(iPos) - planetRadius; 66 | 67 | // Calculate the optical depth of the Rayleigh and Mie scattering for this step. 68 | float odStepRlh = exp(-iHeight / shRlh) * iStepSize; 69 | float odStepMie = exp(-iHeight / shMie) * iStepSize; 70 | 71 | // Accumulate optical depth. 72 | iOdRlh += odStepRlh; 73 | iOdMie += odStepMie; 74 | 75 | // Check whether ray hits the earth 76 | vec2 p = rsi(iPos, pSun, planetRadius); 77 | if (p.x > p.y || p.y < 0) { 78 | float mu = dot(pSun, normalize(iPos)); 79 | vec3 t = precomputed_transmittance(length(iPos), mu); 80 | 81 | // Calculate attenuation. 82 | vec3 attn = exp(-kRlh * iOdRlh) * exp(-kMie * iOdMie) * t; 83 | 84 | // Accumulate scattering. 85 | totalRlh += odStepRlh * attn; 86 | totalMie += odStepMie * attn; 87 | } 88 | 89 | // Increment the primary ray time. 90 | iTime += iStepSize; 91 | } 92 | 93 | // Calculate and return the final color. 94 | float mu = dot(r, pSun); 95 | return iSun * (rayleigh_phase(mu) * kRlh * totalRlh + mie_phase(mu) * kMie * totalMie); 96 | } 97 | 98 | // void reverse_parameters(float r, float mu, float mu_s, 99 | // out float u_r, out float u_mu, out float u_mu_s) { 100 | // float H = sqrt(atmosphereRadius * atmosphereRadius - planetRadius * planetRadius); 101 | // float rho = sqrt(max(r * r - planetRadius * planetRadius, 0)); 102 | // float delta = r * r * mu * mu - rho * rho; 103 | // 104 | // u_r = rho / H; 105 | // 106 | // ivec3 size = textureSize(inscattering, 0); 107 | // 108 | // float hp = (size.y*0.5 - 1.0) / (size.y-1.0); 109 | // float mu_horizon = -sqrt(1.0 - (planetRadius / r) * (planetRadius / r)); 110 | // if (mu > mu_horizon) { 111 | // u_mu = (1.0 - hp) + hp * pow((mu - mu_horizon) / (1.0 - mu_horizon), 0.2); 112 | // } else { 113 | // u_mu = hp * pow((mu_horizon - mu) / (1.0 + mu_horizon), 0.2); 114 | // } 115 | // 116 | // u_mu_s = clamp(0.5*(atan(max(mu_s, -0.45)*tan(1.26 * 0.75)) 117 | // / 0.75 + (1.0 - 0.26)), 0, 1); 118 | // } 119 | // 120 | vec3 precomputed_transmittance(float r, float mu) { 121 | vec2 size = textureSize(transmittance, 0); 122 | 123 | float H = sqrt(atmosphereRadius * atmosphereRadius - planetRadius * planetRadius); 124 | float rho = sqrt(r * r - planetRadius * planetRadius); 125 | float u_r = clamp(rho / H, 0, 1); 126 | 127 | float u_mu; 128 | float hp = (size.y*0.5 - 1.0) / (size.y-1.0); 129 | 130 | float mu_horizon = -sqrt(r * r - planetRadius * planetRadius) / r; 131 | if (mu > mu_horizon) { 132 | float uu = pow((mu - mu_horizon) / (1.0 - mu_horizon), 0.2); 133 | u_mu = uu * hp + (1.0 - hp); 134 | } else { 135 | float uu = pow((mu_horizon - mu) / (1.0 + mu_horizon), 0.2); 136 | u_mu = uu * hp; 137 | } 138 | 139 | return textureLod(sampler2D(transmittance, nearest), (vec2(u_r, u_mu) * (size-1) + 0.5) / size, 0).rgb; 140 | } 141 | 142 | vec3 precomputed_transmittance2(vec3 x, vec3 y) { 143 | float r1 = length(x); 144 | float r2 = length(y); 145 | float mu1 = dot(normalize(x), normalize(x - y)); 146 | float mu2 = dot(normalize(y), normalize(x - y)); 147 | 148 | vec2 size = textureSize(transmittance, 0); 149 | float hp = (size.y*0.5 - 1.0) / (size.y-1.0); 150 | 151 | float mu1_horizon = -sqrt(1.0 - (planetRadius / r1) * (planetRadius / r1)); 152 | float mu2_horizon = -sqrt(1.0 - (planetRadius / r2) * (planetRadius / r2)); 153 | 154 | float H = sqrt(atmosphereRadius * atmosphereRadius - planetRadius * planetRadius); 155 | float rho1 = sqrt(max(r1 * r1 - planetRadius * planetRadius, 0)); 156 | float rho2 = sqrt(max(r2 * r2 - planetRadius * planetRadius, 0)); 157 | float u_r1 = rho1 / H; 158 | float u_r2 = rho2 / H; 159 | 160 | float u_mu1, u_mu2; 161 | if (mu1 > mu1_horizon) { 162 | u_mu1 = (1.0 - hp) + hp * pow((mu1 - mu1_horizon) / (1.0 - mu1_horizon), 0.2); 163 | u_mu2 = (1.0 - hp) + hp * pow(max(mu2 - mu2_horizon, 0) / (1.0 - mu2_horizon), 0.2); 164 | } else { 165 | u_mu1 = hp * pow((mu1_horizon - mu1) / (1.0 + mu1_horizon), 0.2); 166 | u_mu2 = hp * pow(max(mu2_horizon - mu2, 0) / (1.0 + mu2_horizon), 0.2); 167 | } 168 | 169 | vec3 t1 = textureLod(sampler2D(transmittance, nearest), (vec2(u_r1, u_mu1) * (size-1) + 0.5) / size, 0).rgb; 170 | vec3 t2 = textureLod(sampler2D(transmittance, nearest), (vec2(u_r2, u_mu2) * (size-1) + 0.5) / size, 0).rgb; 171 | 172 | return t2 / t1; 173 | } 174 | 175 | // vec3 precomputed_atmosphere(vec3 x, vec3 x0, vec3 sun_normalized) { 176 | // // if (dot(x, sun_normalized) < 0) 177 | // // return vec3(0); 178 | // 179 | // vec3 v_normalized = normalize(x0 - x); 180 | // vec3 x_normalized = normalize(x); 181 | // 182 | // float r = clamp(length(x), planetRadius, atmosphereRadius); 183 | // float mu = dot(v_normalized, x_normalized); 184 | // float mu_s = dot(sun_normalized, x_normalized); 185 | // float v = dot(v_normalized, sun_normalized); 186 | // 187 | // float u_r, u_mu, u_mu_s; 188 | // reverse_parameters(r, mu, mu_s, u_r, u_mu, u_mu_s); 189 | // 190 | // // if (mu_s < -0.45) 191 | // // return vec3(0); 192 | // 193 | // if(u_mu <= 0.5) 194 | // u_mu = clamp(u_mu, 0.0, 0.5 - 0.5 / textureSize(inscattering,0).y); 195 | // else 196 | // u_mu = clamp(u_mu, 0.5 + 0.5 / textureSize(inscattering,0).y, 1.0); 197 | // 198 | // // return vec3(fract((u_mu - 0.5)*128)); 199 | // 200 | // vec4 t = texture(sampler3D(inscattering, linear), vec3(u_r, u_mu, u_mu_s)); 201 | // vec3 rayleigh = t.rgb * rayleigh_phase(v); 202 | // vec3 mie = t.rgb * t.a / t.r * mie_phase(v) / rayleigh_Bs; 203 | // return rayleigh + mie; 204 | // } 205 | // 206 | // vec3 precomputed_atmosphere2(vec3 x, vec3 x0, vec3 sun_normalized, out float u_mu, 207 | // bool force_miss_ground, bool force_hit_ground) { 208 | // vec3 v_normalized = normalize(x0 - x); 209 | // vec3 x_normalized = normalize(x); 210 | // 211 | // float r = max(length(x), planetRadius); 212 | // if(r > atmosphereRadius) { 213 | // vec2 p = rsi(x, v_normalized, atmosphereRadius); 214 | // if (p.x > p.y || p.y < 0.0) { 215 | // return vec3(0); 216 | // } 217 | // x = x + v_normalized * max(p.x, 0.0); 218 | // x_normalized = normalize(x); 219 | // r = length(x); 220 | // } 221 | // float mu = dot(v_normalized, x_normalized); 222 | // float mu_s = dot(sun_normalized, x_normalized); 223 | // float v = dot(v_normalized, sun_normalized); 224 | // 225 | // float u_r, u_mu_s; 226 | // reverse_parameters(r, mu, mu_s, u_r, u_mu, u_mu_s); 227 | // 228 | // if(force_hit_ground) { 229 | // u_mu = min(u_mu, 0.5 - 0.5 / textureSize(inscattering,0).y); 230 | // } else if(force_miss_ground) { 231 | // u_mu = max(u_mu, 0.5 + 0.5 / textureSize(inscattering,0).y); 232 | // } 233 | // 234 | // if(force_miss_ground && u_mu <= 0.5) { 235 | // u_mu = 1.0; 236 | // } else if(force_hit_ground && u_mu >= 0.5) { 237 | // u_mu = 0.0; 238 | // } 239 | // 240 | // vec4 t = texture(sampler3D(inscattering, linear), vec3(u_r, u_mu, u_mu_s)); 241 | // vec3 rayleigh = t.rgb*rayleigh_phase(v); 242 | // vec3 mie = t.rgb * t.a / max(t.r,1e-9) * mie_phase(v); 243 | // 244 | // return rayleigh + mie; 245 | // } 246 | // 247 | // vec3 precomputed_aerial_perspective(vec3 color, vec3 x1, vec3 x0, vec3 sun_normalized) { 248 | // // vec4 hwp = worldToWarped * vec4(position, 1); 249 | // // vec4 hwc = worldToWarped * vec4(cameraPosition, 1); 250 | // // vec3 wp = hwp.xyz / hwp.w; 251 | // // vec3 wc = hwc.xyz / hwc.w; 252 | // 253 | // vec3 r = normalize(x1 - x0); 254 | // vec2 p = rsi(x0, r, atmosphereRadius); 255 | // if (p.x > p.y || p.y <= 0.0) 256 | // return color; 257 | // 258 | // vec3 wp = x1; 259 | // vec3 wc = x0 + r * max(p.x, 0.0); 260 | // vec3 ws = sun_normalized; 261 | // 262 | // if (dot(sun_normalized, x1) < 0) { 263 | // float d = length(cross(wp, sun_normalized)); 264 | // 265 | // vec3 u = cross(wp, sun_normalized); 266 | // vec3 c = normalize(cross(sun_normalized, u)); 267 | // 268 | // wp -= r * min((planetRadius - d), distance(x0, x1)); 269 | // 270 | // // return vec3(c.xyz/10); 271 | // // vec3 c = cross(x1, sun_normalized); 272 | // // vec3 r = normalize(cross(sun_normalized, c)) * planetRadius; 273 | // // return color; 274 | // } 275 | // 276 | // vec3 t = precomputed_transmittance2(wc, wp); 277 | // 278 | // float u_mu; 279 | // vec3 i0 = precomputed_atmosphere2(wc, wp, ws, u_mu, false, false); 280 | // vec3 i1 = precomputed_atmosphere2(wp, wp + (wp - wc), ws, u_mu, u_mu > 0.5, u_mu < 0.5); 281 | // vec3 inscattering = i0 - t * i1; 282 | // 283 | // // if(u_mu < 0.5) return vec3(0); 284 | // // return 100 * (i1 - t * i0); 285 | // // if(inscattering.x <= 0 || inscattering.y <= 0 || inscattering.z <= 0) 286 | // // return vec3(1,0,0); 287 | // 288 | // // if(distance(wc, wp) > 100000) return vec3(1,0,0); 289 | // // if(t.x > .991) return vec3(0); 290 | // // if(u_mu > 0.5) return vec3(0); 291 | // 292 | // return max(inscattering, vec3(0)) + color; 293 | // } 294 | -------------------------------------------------------------------------------- /src/shaders/bounding-sphere.comp: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | 4 | layout(local_size_x = 32) in; 5 | 6 | layout(std140, binding = 0) uniform UniformBlock { 7 | GenMeshUniforms ubo; 8 | }; 9 | 10 | layout(std430, binding = 1) readonly buffer IndirectBlock { 11 | Indirect indirect[]; 12 | } mesh_indirect; 13 | 14 | struct Sphere { 15 | vec3 center; 16 | float radius; 17 | }; 18 | layout(std430, binding = 2) buffer BoundingBlock { 19 | Sphere bounds[]; 20 | } mesh_bounding; 21 | 22 | struct Entry { 23 | vec3 position; 24 | float angle; 25 | vec3 albedo; 26 | float slant; 27 | vec2 texcoord; 28 | vec2 _padding1; 29 | vec4 _padding2; 30 | }; 31 | layout(std430, binding = 3) readonly buffer DataBlock { 32 | Entry entries[]; 33 | } grass_storage; 34 | 35 | shared vec3 min_positions[32]; 36 | shared vec3 max_positions[32]; 37 | shared float max_radius2[32]; 38 | 39 | shared vec3 center; 40 | 41 | void main() { 42 | uint storage_slot = ubo.storage_base_entry + gl_WorkGroupID.x; 43 | uint mesh_slot = ubo.mesh_base_entry + gl_WorkGroupID.x; 44 | 45 | uint max_index = mesh_indirect.indirect[storage_slot].vertex_count / 15; 46 | 47 | vec3 position = grass_storage.entries[storage_slot*1024+gl_LocalInvocationID.x].position; 48 | min_positions[gl_LocalInvocationID.x] = position; 49 | max_positions[gl_LocalInvocationID.x] = position; 50 | 51 | for (int i = 32; i < 32*32; i += 32) { 52 | if (i + gl_LocalInvocationID.x < max_index) { 53 | position = grass_storage.entries[storage_slot*1024+gl_LocalInvocationID.x + i].position; 54 | min_positions[gl_LocalInvocationID.x] = min(min_positions[gl_LocalInvocationID.x], position); 55 | max_positions[gl_LocalInvocationID.x] = max(max_positions[gl_LocalInvocationID.x], position); 56 | } 57 | } 58 | 59 | barrier(); 60 | 61 | if (gl_LocalInvocationID.x == 0) { 62 | for (int i = 1; i < 32; i++) { 63 | min_positions[0] = min(min_positions[0], min_positions[gl_LocalInvocationID.x]); 64 | max_positions[0] = max(max_positions[0], max_positions[gl_LocalInvocationID.x]); 65 | } 66 | center = (min_positions[0] + max_positions[0]) * 0.5; 67 | } 68 | 69 | barrier(); 70 | 71 | max_radius2[gl_LocalInvocationID.x] = 0; 72 | for (int i = 0; i < 32*32; i += 32) { 73 | if (i + gl_LocalInvocationID.x < max_index) { 74 | vec3 v = grass_storage.entries[storage_slot*1024+gl_LocalInvocationID.x + i].position - center; 75 | float radius2 = dot(v, v); 76 | max_radius2[gl_LocalInvocationID.x] = max(max_radius2[gl_LocalInvocationID.x], radius2); 77 | } 78 | } 79 | 80 | barrier(); 81 | 82 | if (gl_LocalInvocationID.x == 0) { 83 | for (int i = 1; i < 32; i++) { 84 | max_radius2[0] = max(max_radius2[0], max_radius2[gl_LocalInvocationID.x]); 85 | } 86 | mesh_bounding.bounds[mesh_slot].center = center; 87 | mesh_bounding.bounds[mesh_slot].radius = sqrt(max_radius2[0]) + 0.25; 88 | } 89 | } -------------------------------------------------------------------------------- /src/shaders/bounding-tree-billboards.comp: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | 4 | layout(local_size_x = 32) in; 5 | 6 | layout(std140, binding = 0) uniform UniformBlock { 7 | GenMeshUniforms ubo; 8 | }; 9 | 10 | layout(std430, binding = 1) readonly buffer IndirectBlock { 11 | Indirect indirect[]; 12 | } mesh_indirect; 13 | 14 | struct Sphere { 15 | vec3 center; 16 | float radius; 17 | }; 18 | layout(std430, binding = 2) buffer BoundingBlock { 19 | Sphere bounds[]; 20 | } mesh_bounding; 21 | 22 | struct Entry { 23 | vec3 position; 24 | float angle; 25 | vec3 albedo; 26 | float height; 27 | vec4 padding0; 28 | vec4 padding1; 29 | }; 30 | layout(std430, binding = 3) readonly buffer DataBlock { 31 | Entry entries[]; 32 | } tree_billboards_storage; 33 | 34 | 35 | shared vec3 min_positions[32]; 36 | shared vec3 max_positions[32]; 37 | shared float max_radius2[32]; 38 | 39 | shared vec3 center; 40 | 41 | void main() { 42 | uint storage_slot = ubo.storage_base_entry + gl_WorkGroupID.x; 43 | uint mesh_slot = ubo.mesh_base_entry + gl_WorkGroupID.x; 44 | 45 | uint max_index = mesh_indirect.indirect[storage_slot].vertex_count / 6; 46 | 47 | vec3 position = tree_billboards_storage.entries[storage_slot*1024 + gl_LocalInvocationID.x].position; 48 | min_positions[gl_LocalInvocationID.x] = position; 49 | max_positions[gl_LocalInvocationID.x] = position; 50 | 51 | for (int i = 32; i < 32*32; i += 32) { 52 | if (i + gl_LocalInvocationID.x < max_index) { 53 | position = tree_billboards_storage.entries[storage_slot*1024 + gl_LocalInvocationID.x + i].position; 54 | min_positions[gl_LocalInvocationID.x] = min(min_positions[gl_LocalInvocationID.x], position); 55 | max_positions[gl_LocalInvocationID.x] = max(max_positions[gl_LocalInvocationID.x], position); 56 | } 57 | } 58 | 59 | barrier(); 60 | 61 | if (gl_LocalInvocationID.x == 0) { 62 | for (int i = 1; i < 32; i++) { 63 | min_positions[0] = min(min_positions[0], min_positions[gl_LocalInvocationID.x]); 64 | max_positions[0] = max(max_positions[0], max_positions[gl_LocalInvocationID.x]); 65 | } 66 | center = (min_positions[0] + max_positions[0]) * 0.5; 67 | } 68 | 69 | barrier(); 70 | 71 | max_radius2[gl_LocalInvocationID.x] = 0; 72 | for (int i = 0; i < 32*32; i += 32) { 73 | if (i + gl_LocalInvocationID.x < max_index) { 74 | vec3 v = tree_billboards_storage.entries[storage_slot*1024 + gl_LocalInvocationID.x + i].position - center; 75 | float radius2 = dot(v, v); 76 | max_radius2[gl_LocalInvocationID.x] = max(max_radius2[gl_LocalInvocationID.x], radius2); 77 | } 78 | } 79 | 80 | barrier(); 81 | 82 | if (gl_LocalInvocationID.x == 0) { 83 | for (int i = 1; i < 32; i++) { 84 | max_radius2[0] = max(max_radius2[0], max_radius2[gl_LocalInvocationID.x]); 85 | } 86 | mesh_bounding.bounds[mesh_slot].center = center; 87 | mesh_bounding.bounds[mesh_slot].radius = sqrt(max_radius2[0]) + 400.0; 88 | } 89 | } -------------------------------------------------------------------------------- /src/shaders/cull-meshes.comp: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | 4 | layout(local_size_x = 64) in; 5 | 6 | layout(set = 0, binding = 0, std140) uniform GlobalBlock { 7 | Globals globals; 8 | }; 9 | layout(set = 0, binding = 1, std140) readonly buffer Nodes { 10 | Node nodes[]; 11 | }; 12 | 13 | /*coherent*/ layout(std430, binding = 2) buffer IndirectBlock { 14 | Indirect indirect[]; 15 | } mesh_indirect; 16 | 17 | struct Sphere { 18 | vec3 center; 19 | float radius; 20 | }; 21 | layout(std430, binding = 3) buffer BoundingBlock { 22 | Sphere bounds[]; 23 | } mesh_bounding; 24 | 25 | struct NodeEntry { 26 | vec3 relative_position; 27 | uint valid; 28 | }; 29 | layout(set = 0, binding = 4, std140) uniform UniformBlock { 30 | uint base_entry; 31 | uint num_nodes; 32 | uint entries_per_node; 33 | uint base_slot; 34 | uint mesh_index; 35 | } ubo; 36 | 37 | void main() { 38 | if (gl_GlobalInvocationID.x > ubo.num_nodes * ubo.entries_per_node) 39 | return; 40 | 41 | uint entry = ubo.base_entry + gl_GlobalInvocationID.x; 42 | mesh_indirect.indirect[entry].base_instance = ubo.base_slot * ubo.entries_per_node + gl_GlobalInvocationID.x; 43 | Node node = nodes[ubo.base_slot + gl_GlobalInvocationID.x / ubo.entries_per_node]; 44 | 45 | if ((node.mesh_valid_mask[ubo.mesh_index] & (1 << (gl_GlobalInvocationID.x % ubo.entries_per_node))) == 0) { 46 | mesh_indirect.indirect[entry].instance_count = 0; 47 | return; 48 | } 49 | 50 | Sphere sphere = mesh_bounding.bounds[entry]; 51 | float d0 = dot(sphere.center.xyz - node.relative_position, globals.frustum_planes[0].xyz) + globals.frustum_planes[0].w; 52 | float d1 = dot(sphere.center.xyz - node.relative_position, globals.frustum_planes[1].xyz) + globals.frustum_planes[1].w; 53 | float d2 = dot(sphere.center.xyz - node.relative_position, globals.frustum_planes[2].xyz) + globals.frustum_planes[2].w; 54 | float d3 = dot(sphere.center.xyz - node.relative_position, globals.frustum_planes[3].xyz) + globals.frustum_planes[3].w; 55 | float d4 = dot(sphere.center.xyz - node.relative_position, globals.frustum_planes[4].xyz) + globals.frustum_planes[4].w; 56 | 57 | if ((d0 < -sphere.radius) || 58 | (d1 < -sphere.radius) || 59 | (d2 < -sphere.radius) || 60 | (d3 < -sphere.radius) || 61 | (d4 < -sphere.radius)) { 62 | mesh_indirect.indirect[entry].instance_count = 0; 63 | } else { 64 | mesh_indirect.indirect[entry].instance_count = 1; 65 | } 66 | } -------------------------------------------------------------------------------- /src/shaders/declarations.glsl: -------------------------------------------------------------------------------- 1 | #extension GL_EXT_samplerless_texture_functions: require 2 | 3 | #ifndef xdouble 4 | #define xdouble uvec2 5 | #endif 6 | 7 | struct Globals { 8 | mat4 view_proj; 9 | mat4 view_proj_inverse; 10 | mat4 shadow_view_proj; 11 | vec4 frustum_planes[5]; 12 | vec3 camera; 13 | float screen_width; 14 | vec3 sun_direction; 15 | float screen_height; 16 | float sidereal_time; 17 | float exposure; 18 | }; 19 | 20 | struct Indirect { 21 | uint vertex_count; 22 | uint instance_count; 23 | uint base_index; 24 | uint vertex_offset; 25 | uint base_instance; 26 | }; 27 | 28 | struct Layer { 29 | vec2 origin; 30 | float ratio; 31 | int slot; 32 | }; 33 | 34 | struct Node { 35 | Layer layers[48]; 36 | 37 | vec3 node_center; 38 | int parent; 39 | 40 | vec3 relative_position; 41 | float min_distance; 42 | 43 | uvec4 mesh_valid_mask; 44 | 45 | uint face; 46 | uint level; 47 | uvec2 coords; 48 | 49 | vec4 padding[12]; 50 | }; 51 | 52 | struct GenMeshUniforms { 53 | uint slot; 54 | uint storage_base_entry; 55 | uint mesh_base_entry; 56 | uint entries_per_node; 57 | }; 58 | 59 | float encode_height(float height) { 60 | return (height + 1024.0) * (1 / 16383.75); 61 | } 62 | float extract_height(float encoded) { 63 | return encoded * 16383.75 - 1024.0; 64 | } 65 | 66 | vec3 layer_texcoord(Layer layer, vec2 texcoord) { 67 | return vec3(layer.origin + layer.ratio * texcoord, layer.slot); 68 | } 69 | 70 | const uint NUM_LAYERS = 24; 71 | 72 | const uint BASE_HEIGHTMAPS_LAYER = 0; 73 | const uint DISPLACEMENTS_LAYER = 1; 74 | const uint ALBEDO_LAYER = 2; 75 | const uint NORMALS_LAYER = 3; 76 | const uint GRASS_CANOPY_LAYER = 4; 77 | const uint TREE_ATTRIBUTES_LAYER = 5; 78 | const uint AERIAL_PERSPECTIVE_LAYER = 6; 79 | const uint BENT_NORMALS_LAYER = 7; 80 | const uint TREECOVER_LAYER = 8; 81 | const uint BASE_ALBEDO_LAYER = 9; 82 | const uint ROOT_AERIAL_PERSPECTIVE_LAYER = 10; 83 | const uint LAND_FRACTION_LAYER = 11; 84 | const uint ELLIPSOID_LAYER = 12; 85 | const uint HEIGHTMAPS_LAYER = 13; 86 | const uint WATERLEVEL_LAYER = 14; 87 | 88 | const uint PARENT_BASE_HEIGHTMAPS_LAYER = NUM_LAYERS + BASE_HEIGHTMAPS_LAYER; 89 | const uint PARENT_DISPLACEMENTS_LAYER = NUM_LAYERS + DISPLACEMENTS_LAYER; 90 | const uint PARENT_ALBEDO_LAYER = NUM_LAYERS + ALBEDO_LAYER; 91 | const uint PARENT_NORMALS_LAYER = NUM_LAYERS + NORMALS_LAYER; 92 | const uint PARENT_GRASS_CANOPY_LAYER = NUM_LAYERS + GRASS_CANOPY_LAYER; 93 | const uint PARENT_TREE_ATTRIBUTES_LAYER = NUM_LAYERS + TREE_ATTRIBUTES_LAYER; 94 | const uint PARENT_AERIAL_PERSPECTIVE_LAYER = NUM_LAYERS + AERIAL_PERSPECTIVE_LAYER; 95 | const uint PARENT_TREECOVER_LAYER = NUM_LAYERS + TREECOVER_LAYER; 96 | 97 | const uint SLOTS_PER_LAYER = 30; 98 | const uint TREE_ATTRIBUTES_BASE_SLOT = 30 + (11 - 2) * SLOTS_PER_LAYER; 99 | const uint GRASS_CANOPY_BASE_SLOT = 30 + (14 - 2) * SLOTS_PER_LAYER; 100 | const uint GRASS_BASE_SLOT = 30 + (19 - 2) * SLOTS_PER_LAYER; 101 | const uint TREE_BILLBOARDS_BASE_SLOT = 30 + (13 - 2) * SLOTS_PER_LAYER; 102 | const uint AERIAL_PERSPECTIVE_BASE_SLOT = 30 + SLOTS_PER_LAYER; 103 | 104 | const uint HEIGHTMAP_INNER_RESOLUTION = 512; 105 | const uint HEIGHTMAP_BORDER = 4; 106 | const uint HEIGHTMAP_RESOLUTION = 521; 107 | 108 | const uint DISPLACEMENTS_INNER_RESOLUTION = 64; 109 | 110 | const uint MAX_BASE_HEIGHTMAP_LEVEL = 8; 111 | const uint MAX_HEIGHTMAP_LEVEL = 12; 112 | -------------------------------------------------------------------------------- /src/shaders/declarations.wgsl: -------------------------------------------------------------------------------- 1 | struct Layer { 2 | origin: vec2, 3 | ratio: f32, 4 | slot: i32, 5 | }; 6 | 7 | struct Node { 8 | layers: array, 9 | 10 | node_center: vec3, 11 | parent: u32, 12 | 13 | relative_position: vec3, 14 | min_distance: f32, 15 | 16 | mesh_valid_mask: array, 17 | 18 | face: u32, 19 | level: u32, 20 | coords: vec2, 21 | 22 | padding2: array, 12>, 23 | }; 24 | struct Nodes { 25 | entries: array, 26 | }; 27 | 28 | struct GenMeshUniforms { 29 | slot: u32, 30 | storage_base_entry: u32, 31 | mesh_base_entry: u32, 32 | entries_per_node: u32, 33 | }; 34 | 35 | struct Indirect { 36 | vertex_count: atomic, // TODO: why doesn't u32 work here? 37 | instance_count: u32, 38 | base_index: u32, 39 | vertex_offset: u32, 40 | base_instance: u32, 41 | }; 42 | struct Indirects { 43 | entries: array, 44 | }; 45 | 46 | const NUM_LAYERS: u32 = 24u; 47 | 48 | const BASE_HEIGHTMAPS_LAYER: u32 = 0u; 49 | const DISPLACEMENTS_LAYER: u32 = 1u; 50 | const ALBEDO_LAYER: u32 = 2u; 51 | const NORMALS_LAYER: u32 = 3u; 52 | const GRASS_CANOPY_LAYER: u32 = 4u; 53 | const TREE_ATTRIBUTES_LAYER: u32 = 5u; 54 | const AERIAL_PERSPECTIVE_LAYER: u32 = 6u; 55 | const BENT_NORMALS_LAYER: u32 = 7u; 56 | 57 | const PARENT_HEIGHTMAPS_LAYER: u32 = 24u; 58 | const PARENT_DISPLACEMENTS_LAYER: u32 = 25u; 59 | const PARENT_ALBEDO_LAYER: u32 = 26u; 60 | const PARENT_NORMALS_LAYER: u32 = 27u; 61 | const PARENT_GRASS_CANOPY_LAYER: u32 = 28u; 62 | const PARENT_TREE_ATTRIBUTES_LAYER: u32 = 30u; 63 | 64 | const GRASS_BASE_SLOT: u32 = 540u;//30 + (19 - 2) * 30; 65 | 66 | fn hash(x: u32) -> u32 { 67 | var xx = x; 68 | xx = xx + ( xx << 10u ); 69 | xx = xx ^ ( xx >> 6u ); 70 | xx = xx + ( xx << 3u ); 71 | xx = xx ^ ( xx >> 11u ); 72 | xx = xx + ( xx << 15u ); 73 | return xx; 74 | } 75 | fn hash2(v: vec2 ) -> u32 { return hash( v.x ^ hash(v.y) ); } 76 | fn hash3(v: vec3 ) -> u32 { return hash( v.x ^ hash(v.y) ^ hash(v.z) ); } 77 | fn hash4(v: vec4 ) -> u32 { return hash( v.x ^ hash(v.y) ^ hash(v.z) ^ hash(v.w) ); } 78 | fn floatConstruct(m: u32) -> f32 { return bitcast((m & 0x007FFFFFu) | 0x3F800000u) - 1.0; } 79 | fn random(x: f32) -> f32 { return floatConstruct(hash(bitcast(x))); } 80 | fn random2(x: vec2) -> f32 { return floatConstruct(hash2(bitcast>(x))); } 81 | fn random3(x: vec3) -> f32 { return floatConstruct(hash3(bitcast>(x))); } 82 | fn random4(x: vec4) -> f32 { return floatConstruct(hash4(bitcast>(x))); } 83 | 84 | fn extract_normal(n: vec2) -> vec3 { 85 | let n = n * 2.0 - vec2(1.0); 86 | let y = sqrt(max(1.0 - dot(n, n), 0.0)); 87 | return normalize(vec3(n.x, y, n.y)); 88 | } 89 | 90 | fn layer_texcoord(layer: Layer, texcoord: vec2) -> vec2 { 91 | return layer.origin + layer.ratio * texcoord; 92 | } 93 | -------------------------------------------------------------------------------- /src/shaders/gen-aerial-perspective.comp: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | 4 | layout(local_size_x = 17, local_size_y = 17) in; 5 | 6 | layout(set = 0, binding = 0, std140) uniform GlobalsBlock { 7 | Globals globals; 8 | }; 9 | layout(set = 0, binding = 1, std140) readonly buffer Nodes { 10 | Node nodes[]; 11 | }; 12 | layout(binding = 2, std430) readonly buffer UniformBlock { 13 | uint node_list[1024]; 14 | } ubo; 15 | 16 | layout(set = 0, binding = 3) uniform sampler nearest; 17 | layout(set = 0, binding = 4) uniform texture2DArray displacements; 18 | layout(set = 0, binding = 5) uniform texture2D transmittance; 19 | layout(rgba16f, binding = 6) writeonly uniform image2DArray aerial_perspective; 20 | 21 | #include "atmosphere.glsl" 22 | 23 | const vec3 ellipsoid_to_sphere = vec3(1, 1, 1.0033640898210048); 24 | 25 | void main() { 26 | uint slot = ubo.node_list[gl_GlobalInvocationID.z]; 27 | Node node = nodes[slot]; 28 | 29 | ivec2 iPosition = ivec2(gl_GlobalInvocationID.xy); 30 | vec3 texcoord = layer_texcoord(node.layers[DISPLACEMENTS_LAYER], vec2(iPosition) / 16.0); 31 | vec3 position = textureLod(sampler2DArray(displacements, nearest), texcoord, 0).xyz 32 | - nodes[node.layers[DISPLACEMENTS_LAYER].slot].relative_position; 33 | 34 | vec3 x0 = globals.camera * ellipsoid_to_sphere; 35 | vec3 x1 = (globals.camera + position) * ellipsoid_to_sphere; 36 | vec3 r = normalize(x1 - x0); 37 | vec2 p = rsi(x0, r, atmosphereRadius); 38 | 39 | vec4 output_value = vec4(0, 0, 0, 1); 40 | if (p.x < p.y && p.y >= 0) { 41 | x0 += r * max(p.x, 0.0); 42 | output_value.a = precomputed_transmittance2(x1, x0).b; 43 | output_value.rgb = atmosphere(x0, x1, globals.sun_direction) * vec3(1.0 / 16.0); 44 | } 45 | 46 | imageStore(aerial_perspective, ivec3(gl_GlobalInvocationID.xy, slot - AERIAL_PERSPECTIVE_BASE_SLOT), output_value); 47 | } 48 | -------------------------------------------------------------------------------- /src/shaders/gen-bent-normals.comp: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | #include "hash.glsl" 4 | 5 | layout(local_size_x = 8, local_size_y = 8) in; 6 | 7 | layout(binding = 0) readonly buffer UniformBlock { 8 | int slots[]; 9 | } ubo; 10 | 11 | layout(binding = 1) uniform texture2DArray heightmaps; 12 | layout(rgba8, binding = 2) writeonly uniform image2DArray bent_normals; 13 | 14 | layout(set = 0, binding = 3, std140) readonly buffer Nodes { 15 | Node nodes[]; 16 | }; 17 | 18 | shared float heights[16][16]; 19 | 20 | void main() { 21 | Node node = nodes[ubo.slots[gl_GlobalInvocationID.z]]; 22 | 23 | ivec3 base_pos = ivec3(ivec2(gl_GlobalInvocationID.xy + HEIGHTMAP_BORDER - 4), node.layers[HEIGHTMAPS_LAYER].slot); 24 | heights[gl_LocalInvocationID.x][gl_LocalInvocationID.y] = extract_height(texelFetch(heightmaps, base_pos, 0).x); 25 | heights[gl_LocalInvocationID.x+8][gl_LocalInvocationID.y] = extract_height(texelFetch(heightmaps, base_pos+ivec3(8,0,0), 0).x); 26 | heights[gl_LocalInvocationID.x][gl_LocalInvocationID.y+8] = extract_height(texelFetch(heightmaps, base_pos+ivec3(0,8,0), 0).x); 27 | heights[gl_LocalInvocationID.x+8][gl_LocalInvocationID.y+8] = extract_height(texelFetch(heightmaps, base_pos+ivec3(8,8,0), 0).x); 28 | barrier(); 29 | 30 | float spacing = 19545.9832 / float(1 << node.level); 31 | 32 | vec4 value = vec4(1); 33 | float height = heights[gl_LocalInvocationID.x+4][gl_LocalInvocationID.y+4]; 34 | 35 | for (int y = -2; y <= 2; y++) { 36 | for (int x = -2; x <= 2; x++) { 37 | if (x != 0 || y != 0) { 38 | float h = heights[gl_LocalInvocationID.x+x+4][gl_LocalInvocationID.y+y+4] - height; 39 | if (h > 0) 40 | value.a -= 1.0 / 24 * smoothstep(spacing*.25, spacing*.5, h); 41 | } 42 | } 43 | } 44 | 45 | imageStore(bent_normals, ivec3(gl_GlobalInvocationID.xy, node.layers[BENT_NORMALS_LAYER].slot), value); 46 | } 47 | -------------------------------------------------------------------------------- /src/shaders/gen-displacements.comp: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | 3 | layout(local_size_x = 8, local_size_y = 8) in; 4 | 5 | #include "declarations.glsl" 6 | 7 | 8 | layout(binding = 0) readonly buffer UniformBlock { 9 | int slots[]; 10 | } ubo; 11 | layout(binding = 1) uniform texture2DArray heightmaps; 12 | layout(binding = 2) uniform texture2DArray base_heightmaps; 13 | layout(binding = 3) uniform texture2DArray ellipsoid; 14 | layout(binding = 4) uniform texture2DArray waterlevel; 15 | layout(rgba32f, binding = 5) writeonly uniform image2DArray displacements; 16 | layout(set = 0, binding = 6, std140) readonly buffer Nodes { 17 | Node nodes[]; 18 | }; 19 | layout(set = 0, binding = 7) uniform sampler linear; 20 | 21 | const float A = 6378137.0; 22 | const float B = 6356752.314245; 23 | 24 | void main() { 25 | if (max(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y) > DISPLACEMENTS_INNER_RESOLUTION) 26 | return; 27 | 28 | Node node = nodes[ubo.slots[gl_GlobalInvocationID.z]]; 29 | vec2 texcoord = vec2(gl_GlobalInvocationID) / float(DISPLACEMENTS_INNER_RESOLUTION); 30 | 31 | float height; 32 | if (node.layers[HEIGHTMAPS_LAYER].slot >= 0) { 33 | height = extract_height(textureLod(sampler2DArray(heightmaps, linear), 34 | layer_texcoord(node.layers[HEIGHTMAPS_LAYER], texcoord), 0).x); 35 | } else { 36 | height = extract_height(textureLod(sampler2DArray(base_heightmaps, linear), 37 | layer_texcoord(node.layers[BASE_HEIGHTMAPS_LAYER], texcoord), 0).x); 38 | } 39 | 40 | float waterlevel_value = 0; 41 | if (node.layers[WATERLEVEL_LAYER].slot >= 0) { 42 | waterlevel_value = extract_height(textureLod(sampler2DArray(waterlevel, linear), 43 | layer_texcoord(node.layers[WATERLEVEL_LAYER], texcoord), 0).x); 44 | } 45 | height = max(height, waterlevel_value); 46 | 47 | vec3 ellipsoid_point = texelFetch(ellipsoid, ivec3(gl_GlobalInvocationID.xy, node.layers[ELLIPSOID_LAYER].slot), 0).xyz; 48 | vec3 position = ellipsoid_point + node.node_center; 49 | 50 | float latitude = atan(position.z * A*A / (B*B), length(position.xy)); 51 | float longitude = atan(position.y, position.x); 52 | vec3 normal = vec3( 53 | cos(latitude) * cos(longitude), 54 | cos(latitude) * sin(longitude), 55 | sin(latitude) 56 | ); 57 | 58 | ivec3 pos = ivec3(gl_GlobalInvocationID.xy, node.layers[DISPLACEMENTS_LAYER].slot); 59 | imageStore(displacements, pos, vec4(ellipsoid_point + normal * height, 0.0)); 60 | } -------------------------------------------------------------------------------- /src/shaders/gen-grass-canopy.comp: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | #include "hash.glsl" 4 | 5 | layout(local_size_x = 8, local_size_y = 8) in; 6 | 7 | layout(set = 0, binding = 0, std140) readonly buffer Nodes { 8 | Node nodes[]; 9 | }; 10 | layout(binding = 1) readonly buffer UniformBlock { 11 | int slots[]; 12 | } ubo; 13 | 14 | layout(binding = 2) uniform sampler linear; 15 | layout(binding = 3) uniform sampler linear_wrap; 16 | layout(binding = 4) uniform texture2DArray normals; 17 | layout(binding = 5) uniform texture2D noise; 18 | layout(binding = 6) uniform texture2DArray heightmaps; 19 | layout(binding = 7) uniform texture2DArray waterlevel; 20 | 21 | layout(rgba8, binding = 8) writeonly uniform image2DArray grass_canopy; 22 | 23 | vec3 extract_normal(vec2 n) { 24 | n = n * 2.0 - vec2(1.0); 25 | float y = sqrt(max(1.0 - dot(n, n),0)); 26 | return normalize(vec3(n.x, y, n.y)); 27 | } 28 | 29 | void main() { 30 | Node node = nodes[ubo.slots[gl_GlobalInvocationID.z]]; 31 | 32 | vec4 value = vec4(0); 33 | 34 | vec3 normal = extract_normal(texelFetch(normals, ivec3(gl_GlobalInvocationID.xy, node.layers[NORMALS_LAYER].slot), 0).xy); 35 | 36 | vec2 texcoord = vec2(gl_GlobalInvocationID.xy-1.5) / vec2(512); 37 | float height = extract_height(textureLod(sampler2DArray(heightmaps, linear), layer_texcoord(node.layers[HEIGHTMAPS_LAYER], texcoord), 0).x); 38 | float water_surface = extract_height(textureLod(sampler2DArray(waterlevel, linear), layer_texcoord(node.layers[WATERLEVEL_LAYER], texcoord),0).x); 39 | 40 | vec3 r3 = vec3(random(uvec3(gl_GlobalInvocationID.xy, 2)), 41 | random(uvec3(gl_GlobalInvocationID.xy, 3)), 42 | random(uvec3(gl_GlobalInvocationID.xy, 4))); 43 | 44 | if(normal.y > 0.97 && height > water_surface + r3.x*.1 + 2.1) 45 | value = vec4(r3 * vec3(.1,.5,.2) + vec3(0,.2,0), 1); 46 | 47 | imageStore(grass_canopy, ivec3(gl_GlobalInvocationID.xy, node.layers[GRASS_CANOPY_LAYER].slot), value); 48 | } 49 | -------------------------------------------------------------------------------- /src/shaders/gen-grass.comp: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | #include "hash.glsl" 4 | 5 | layout(local_size_x = 8, local_size_y = 8) in; 6 | 7 | struct Entry { 8 | vec3 position; 9 | float angle; 10 | vec3 albedo; 11 | float slant; 12 | vec2 texcoord; 13 | vec2 _padding1; 14 | vec4 _padding2; 15 | }; 16 | 17 | layout(binding = 0) uniform UniformBlock { 18 | GenMeshUniforms ubo; 19 | }; 20 | layout(std430, binding = 1) buffer StorageDataBlock { 21 | Entry entries[][32*32]; 22 | } grass_storage; 23 | coherent layout(std430, binding = 2) buffer IndirectBlock { 24 | Indirect indirect[]; 25 | } mesh_indirect; 26 | 27 | layout(set = 0, binding = 3) uniform sampler linear; 28 | layout(set = 0, binding = 4) uniform texture2DArray displacements; 29 | layout(set = 0, binding = 5) uniform texture2DArray normals; 30 | layout(set = 0, binding = 6) uniform texture2DArray albedo; 31 | layout(set = 0, binding = 7) uniform texture2DArray grass_canopy; 32 | layout(set = 0, binding = 8, std140) readonly buffer Nodes { 33 | Node nodes[]; 34 | }; 35 | 36 | vec3 extract_normal(vec2 n) { 37 | n = n * 2.0 - vec2(1.0); 38 | float y = sqrt(max(1.0 - dot(n, n),0)); 39 | return normalize(vec3(n.x, y, n.y)); 40 | } 41 | 42 | vec3 layer_to_texcoord(uint layer) { 43 | vec2 texcoord = (vec2(gl_GlobalInvocationID.xy) /*+ r*/) / 128.0; 44 | return layer_texcoord(nodes[ubo.slot].layers[layer], texcoord); 45 | } 46 | 47 | #define BILINEAR(r, img, v) { \ 48 | vec2 f = fract(v.xy * textureSize(img, 0).xy); \ 49 | vec4 i00 = texelFetch(img, ivec3(v.xy * textureSize(img,0).xy, v.z), 0); \ 50 | vec4 i10 = texelFetch(img, ivec3(v.xy * textureSize(img,0).xy, v.z)+ivec3(1,0,0), 0); \ 51 | vec4 i01 = texelFetch(img, ivec3(v.xy * textureSize(img,0).xy, v.z)+ivec3(0,1,0), 0); \ 52 | vec4 i11 = texelFetch(img, ivec3(v.xy * textureSize(img,0).xy, v.z)+ivec3(1,1,0), 0); \ 53 | r = mix(mix(i00, i10, f.x), mix(i01, i11, f.y), f.y); \ 54 | } 55 | 56 | void main() { 57 | Node node = nodes[ubo.slot]; 58 | 59 | uvec2 index = gl_GlobalInvocationID.xy % 32; 60 | uint entry = 4 * (gl_GlobalInvocationID.y / 32) + (gl_GlobalInvocationID.x / 32); 61 | 62 | if (index == ivec2(0)) { 63 | mesh_indirect.indirect[ubo.mesh_base_entry + entry].instance_count = 1; 64 | } 65 | 66 | vec2 r = vec2(random(uvec3(index, 0)), random(uvec3(index, 1))); 67 | 68 | vec3 normal = extract_normal(texture(sampler2DArray(normals, linear), layer_to_texcoord(NORMALS_LAYER)).xy); 69 | vec3 albedo_value = texture(sampler2DArray(albedo, linear), layer_to_texcoord(ALBEDO_LAYER)).xyz; 70 | vec4 canopy = texture(sampler2DArray(grass_canopy, linear), layer_to_texcoord(GRASS_CANOPY_LAYER)); 71 | 72 | // if (canopy.a < .8+.2*r.x) 73 | // return; 74 | 75 | if (normal.y < 0.95) 76 | return; 77 | 78 | vec3 r3 = vec3(random(uvec3(index, 2)), 79 | random(uvec3(index, 3)), 80 | random(uvec3(index, 4))); 81 | 82 | float angle = random(uvec3(index, 5)) * 2.0 * 3.14159265; 83 | 84 | vec4 position; 85 | BILINEAR(position, displacements, layer_to_texcoord(DISPLACEMENTS_LAYER)) 86 | 87 | uint i = atomicAdd(mesh_indirect.indirect[ubo.mesh_base_entry + entry].vertex_count, 15) / 15; 88 | grass_storage.entries[ubo.storage_base_entry + entry][i].texcoord = layer_to_texcoord(NORMALS_LAYER).xy; 89 | grass_storage.entries[ubo.storage_base_entry + entry][i].position = position.xyz; 90 | grass_storage.entries[ubo.storage_base_entry + entry][i].albedo = ((canopy.rgb - 0.5) * 0.025 + albedo_value) /* * mix(vec3(.5), vec3(1.5), r3) */; 91 | grass_storage.entries[ubo.storage_base_entry + entry][i].angle = angle; 92 | grass_storage.entries[ubo.storage_base_entry + entry][i].slant = r.y; 93 | } -------------------------------------------------------------------------------- /src/shaders/gen-grass.wgsl: -------------------------------------------------------------------------------- 1 | struct Entry { 2 | position: vec3, 3 | angle: f32, 4 | albedo: vec3, 5 | slant: f32, 6 | texcoord: vec2, 7 | padding1: vec2, 8 | padding2: vec4, 9 | }; 10 | struct Entries { 11 | entries: array>, 12 | }; 13 | 14 | @group(0) @binding(0) var ubo: GenMeshUniforms; 15 | @group(0) @binding(1) var grass_storage: Entries; 16 | @group(0) @binding(3) var mesh_indirect: Indirects; 17 | @group(0) @binding(4) var nodes: Nodes; 18 | @group(0) @binding(5) var linearsamp: sampler; 19 | @group(0) @binding(6) var displacements: texture_2d_array; 20 | @group(0) @binding(7) var normals: texture_2d_array; 21 | @group(0) @binding(8) var albedo: texture_2d_array; 22 | @group(0) @binding(9) var grass_canopy: texture_2d_array; 23 | 24 | fn read_texture(layer: u32, global_id: vec3) -> vec4 { 25 | var node = nodes.entries[ubo.slot]; 26 | let texcoord = layer_texcoord(node.layers[layer], vec2(global_id.xy) / 128.0); 27 | let array_index = node.layers[layer].slot; 28 | 29 | let l = layer % NUM_LAYERS; 30 | if (l == ALBEDO_LAYER) { return textureSampleLevel(albedo, linearsamp, texcoord, array_index, 0.0); } 31 | else if (l == NORMALS_LAYER) { return textureSampleLevel(normals, linearsamp, texcoord, array_index, 0.0); } 32 | else if (l == GRASS_CANOPY_LAYER) { return textureSampleLevel(grass_canopy, linearsamp, texcoord, array_index, 0.0); } 33 | else if (l == DISPLACEMENTS_LAYER) { 34 | let dimensions = textureDimensions(displacements); 35 | let f = fract(texcoord.xy * vec2(dimensions)); 36 | let base_coords = vec2(texcoord.xy * vec2(dimensions)); 37 | let i00 = textureLoad(displacements, base_coords, array_index, 0); 38 | let i10 = textureLoad(displacements, base_coords + vec2(1,0), array_index, 0); 39 | let i01 = textureLoad(displacements, base_coords + vec2(0,1), array_index, 0); 40 | let i11 = textureLoad(displacements, base_coords + vec2(1,1), array_index, 0); 41 | return mix(mix(i00, i10, f.x), mix(i01, i11, f.y), f.y); 42 | } 43 | 44 | return vec4(1.0, 0.0, 1.0, 1.0); 45 | } 46 | 47 | @compute 48 | @workgroup_size(8,8) 49 | fn main( 50 | @builtin(global_invocation_id) global_id: vec3, 51 | ) { 52 | let node = nodes.entries[ubo.slot]; 53 | 54 | let index = global_id.xy % vec2(32u); 55 | let entry = 4u * (global_id.y / 32u) + (global_id.x / 32u); 56 | 57 | let rnd1 = random3(vec3(vec2(index), 1.0)); 58 | let rnd2 = random3(vec3(vec2(index), 2.0)); 59 | let rnd3 = random3(vec3(vec2(index), 3.0)); 60 | let rnd4 = random3(vec3(vec2(index), 4.0)); 61 | let rnd5 = random3(vec3(vec2(index), 5.0)); 62 | 63 | // let texcoord = vec2(global_id.xy) / 128.0; 64 | let normal = extract_normal(read_texture(NORMALS_LAYER, global_id).xy); 65 | let albedo_value = read_texture(ALBEDO_LAYER, global_id).xyz; 66 | let canopy = read_texture(GRASS_CANOPY_LAYER, global_id); 67 | 68 | if (normal.y < 0.95 || canopy.w <= rnd1) { 69 | return; 70 | } 71 | 72 | // Sample displacements texture at random offset (rnd1, rnd). 73 | let texcoord = layer_texcoord(node.layers[DISPLACEMENTS_LAYER], (vec2(global_id.xy) + vec2(rnd1, rnd2)) / 128.0); 74 | let array_index = node.layers[DISPLACEMENTS_LAYER].slot; 75 | let dimensions = textureDimensions(displacements); 76 | let stexcoord = max(texcoord.xy * vec2(dimensions) - vec2(0.5), vec2(0.0)); 77 | let f = fract(stexcoord); 78 | let base_coords = vec2(stexcoord - f); 79 | let i00 = textureLoad(displacements, base_coords, array_index, 0); 80 | let i10 = textureLoad(displacements, min(base_coords + vec2(1,0), dimensions-vec2(1)), array_index, 0); 81 | let i01 = textureLoad(displacements, min(base_coords + vec2(0,1), dimensions-vec2(1)), array_index, 0); 82 | let i11 = textureLoad(displacements, min(base_coords + vec2(1,1), dimensions-vec2(1)), array_index, 0); 83 | let position = mix(mix(i00, i10, f.x), mix(i01, i11, f.x), f.y); 84 | 85 | let i = atomicAdd(&mesh_indirect.entries[ubo.mesh_base_entry + entry].vertex_count, 15) / 15; 86 | grass_storage.entries[ubo.storage_base_entry + entry][i].texcoord = texcoord; //layer_to_texcoord(NORMALS_LAYER).xy; 87 | grass_storage.entries[ubo.storage_base_entry + entry][i].position = position.xyz; 88 | grass_storage.entries[ubo.storage_base_entry + entry][i].albedo = ((canopy.rgb - 0.5) * 0.025 + albedo_value) * mix(vec3(.75), vec3(1.25), vec3(rnd2, rnd3, rnd4)); 89 | grass_storage.entries[ubo.storage_base_entry + entry][i].angle = rnd5 * 2.0 * 3.14159265; 90 | grass_storage.entries[ubo.storage_base_entry + entry][i].slant = rnd1; 91 | } 92 | -------------------------------------------------------------------------------- /src/shaders/gen-heightmaps.comp: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | #include "hash.glsl" 4 | 5 | layout(local_size_x = 16, local_size_y = 16) in; 6 | 7 | layout(binding = 0) readonly buffer UniformBlock { 8 | int slots[]; 9 | } ubo; 10 | 11 | layout(binding = 1) uniform texture2DArray base_heightmaps; 12 | layout(r16, binding = 2) writeonly uniform image2DArray heightmaps; 13 | 14 | layout(set = 0, binding = 3, std140) readonly buffer Nodes { 15 | Node nodes[]; 16 | }; 17 | 18 | const uint SIZE = 11; 19 | 20 | shared uint base_heights_level; 21 | shared ivec2 base_heights_origin; 22 | shared float base_heights[SIZE][SIZE]; 23 | shared float heights_working[SIZE][SIZE]; 24 | 25 | vec2 interpolate(uint x, uint y, vec2 t) { 26 | const mat4 M = transpose(mat4( 27 | -.5, 1.5, -1.5, .5, 28 | 1, -2.5, 2, -.5, 29 | -.5, 0, .5, 0, 30 | 0, 1, 0, 0 31 | )); 32 | // const mat4 M = transpose(mat4( 33 | // 0, 0, 0, 0, 34 | // 0, 0, 0, 0, 35 | // 0, -1, 1, 0, 36 | // 0, 1, 0, 0 37 | // )); 38 | 39 | vec4 xweights = vec4(t.x*t.x*t.x, t.x*t.x, t.x, 1) * M; 40 | vec4 yweights = vec4(t.y*t.y*t.y, t.y*t.y, t.y, 1) * M; 41 | 42 | vec4 dxweights = vec4(3*t.x*t.x, 2*t.x, 1, 0) * M; 43 | vec4 dyweights = vec4(3*t.y*t.y, 2*t.y, 1, 0) * M; 44 | 45 | float dx = 0; 46 | float dy = 0; 47 | float height = 0; 48 | for (uint i = 0; i <= 3; i++) { 49 | for (uint j = 0; j <= 3; j++) { 50 | float h = base_heights[x+i-1][y+j-1]; 51 | height += h * xweights[i] * yweights[j]; 52 | dx += h * dxweights[i] * yweights[j]; 53 | dy += h * xweights[i] * dyweights[j]; 54 | } 55 | } 56 | return vec2(height, length(vec2(dx, dy))); 57 | } 58 | 59 | float compute_height(ivec2 v) { 60 | vec2 t = vec2(v % ivec2(2)) / 2.0; 61 | int x = v.x / 2 - base_heights_origin.x; 62 | int y = v.y / 2 - base_heights_origin.y; 63 | 64 | vec2 height_slope = interpolate(uint(x), uint(y), t); 65 | 66 | float spacing = 19545.9832 / float(1 << (base_heights_level+1)); 67 | 68 | float n = random(uvec2(v)) - 0.5; 69 | float delta = n * spacing * mix(0.03, 0.2, smoothstep(0.4, 0.5, height_slope.y / spacing)) * min(abs(height_slope.x*0.5), 1); 70 | 71 | // Make sure seams match. 72 | if (min(v.x, v.y) < 0 || max(v.x, v.y) >= HEIGHTMAP_INNER_RESOLUTION << (base_heights_level+1)) 73 | delta = 0; 74 | 75 | return height_slope.x + delta; 76 | } 77 | 78 | void upscale_heights(ivec2 base) { 79 | uint index = gl_LocalInvocationID.x + gl_LocalInvocationID.y * 16; 80 | for (uint i = index; i < SIZE*SIZE; i += 16*16) { 81 | uint x = i % SIZE; 82 | uint y = i / SIZE; 83 | 84 | heights_working[x][y] = compute_height(base + ivec2(x, y)); 85 | } 86 | barrier(); 87 | 88 | for (uint i = index; i < SIZE*SIZE; i += 16*16) { 89 | uint x = i % SIZE; 90 | uint y = i / SIZE; 91 | base_heights[x][y] = heights_working[x][y]; 92 | } 93 | if (index == 0) { 94 | base_heights_origin = base; 95 | base_heights_level += 1; 96 | } 97 | barrier(); 98 | } 99 | 100 | const uint MAX_STREAMED_HEIGHTMAP_LEVEL = 8; 101 | 102 | void main() { 103 | Node node = nodes[ubo.slots[gl_GlobalInvocationID.z]]; 104 | 105 | int upscale_levels = int(node.level - MAX_BASE_HEIGHTMAP_LEVEL); 106 | ivec2 workgroup_origin = ivec2(node.coords * HEIGHTMAP_INNER_RESOLUTION + gl_WorkGroupID.xy * 16) - ivec2(HEIGHTMAP_BORDER); 107 | ivec2 ancestor_origin = ivec2((node.coords >> upscale_levels) * HEIGHTMAP_INNER_RESOLUTION) - ivec2(HEIGHTMAP_BORDER); 108 | 109 | uvec2 max_offset = uvec2((2 << upscale_levels) - 1); 110 | uvec2 ancestor_coords = (workgroup_origin - max_offset - (ancestor_origin << upscale_levels)) >> upscale_levels; 111 | 112 | // Compute base heights. 113 | uint index = gl_LocalInvocationID.x * 16 + gl_LocalInvocationID.y; 114 | if (index == 0) { 115 | base_heights_level = MAX_BASE_HEIGHTMAP_LEVEL; 116 | base_heights_origin = ancestor_origin + ivec2(ancestor_coords); 117 | } 118 | barrier(); 119 | for (uint i = index; i < SIZE*SIZE; i += 256){ 120 | uvec2 uv = uvec2(i%SIZE, i/SIZE); 121 | base_heights[uv.x][uv.y] = extract_height(texelFetch(base_heightmaps, 122 | ivec3(ancestor_coords + uv, node.layers[BASE_HEIGHTMAPS_LAYER].slot), 0).x); 123 | } 124 | barrier(); 125 | 126 | // Compute upscaled heights. 127 | for (int i = upscale_levels - 1; i > 0; i--) { 128 | upscale_heights((workgroup_origin - ((1 << i) - 1)) >> i); 129 | } 130 | 131 | // Compute and write height. 132 | float height = compute_height(workgroup_origin + ivec2(gl_LocalInvocationID.xy)); 133 | float encoded_height = (height + 1024.0) * (1 / 16384.0); 134 | imageStore(heightmaps, ivec3(gl_GlobalInvocationID.xy, node.layers[HEIGHTMAPS_LAYER].slot), 135 | vec4(encoded_height, 0, 0, 0)); 136 | } 137 | -------------------------------------------------------------------------------- /src/shaders/gen-materials.comp: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | #include "hash.glsl" 4 | 5 | layout(local_size_x = 16, local_size_y = 16) in; 6 | 7 | layout(binding = 0) readonly buffer UniformBlock { 8 | int slots[]; 9 | } ubo; 10 | 11 | layout(rg8, binding = 1) writeonly uniform image2DArray normals; 12 | layout(rgba8, binding = 2) writeonly uniform image2DArray albedo; 13 | 14 | layout(binding = 3) uniform sampler linear; 15 | layout(binding = 4) uniform sampler linear_wrap; 16 | layout(binding = 5) uniform texture2D noise; 17 | layout(binding = 6) uniform texture2DArray base_heightmaps; 18 | layout(binding = 7) uniform texture2DArray base_albedo; 19 | layout(binding = 8) uniform texture2DArray treecover; 20 | layout(binding = 9) uniform texture2DArray tree_attributes; 21 | layout(binding = 10) uniform texture2DArray ground_albedo; 22 | layout(binding = 11) uniform texture2DArray land_fraction; 23 | layout(binding = 12) uniform texture2DArray heightmaps; 24 | layout(binding = 13) uniform texture2DArray waterlevel; 25 | layout(binding = 14) uniform texture2D topdown_albedo; 26 | layout(binding = 15) uniform texture2D topdown_normals; 27 | layout(binding = 16) uniform sampler nearest; 28 | 29 | layout(set = 0, binding = 17, std140) readonly buffer Nodes { 30 | Node nodes[]; 31 | }; 32 | 33 | const uint BASE_ALBEDO_BORDER = 2; 34 | const uint BASE_ALBEDO_INNER_RESOLUTION = 512; 35 | 36 | const uint MATERIAL_BORDER = 2; 37 | const uint MATERIAL_INNER_RESOLUTION = 512; 38 | 39 | const uint NORMALS_BORDER = 2; 40 | 41 | vec3 layer_to_texcoord(uint layer) { 42 | Node node = nodes[ubo.slots[gl_GlobalInvocationID.z]]; 43 | vec2 texcoord = vec2(gl_GlobalInvocationID.xy-1.5) / vec2(512); 44 | return layer_texcoord(node.layers[layer], texcoord); 45 | } 46 | 47 | shared float heights[20][20]; 48 | shared vec3 slopes[18][18]; 49 | 50 | void main() { 51 | Node node = nodes[ubo.slots[gl_GlobalInvocationID.z]]; 52 | 53 | vec2 texcoord = vec2(gl_GlobalInvocationID.xy-1.5) / vec2(512); 54 | 55 | vec3 balbedo = pow(textureLod(sampler2DArray(base_albedo, linear), layer_to_texcoord(BASE_ALBEDO_LAYER), 0).rgb, vec3(2.2)); 56 | float water_amount = 1 - textureLod(sampler2DArray(land_fraction, linear), layer_to_texcoord(LAND_FRACTION_LAYER), 0).x; 57 | 58 | float height = 0; 59 | vec3 normal = vec3(0,1,0); 60 | if (node.level <= MAX_BASE_HEIGHTMAP_LEVEL) { 61 | vec3 hm_texcoord3 = layer_to_texcoord(BASE_HEIGHTMAPS_LAYER); 62 | height = extract_height(textureLod(sampler2DArray(base_heightmaps, linear), hm_texcoord3, 0).x); 63 | float height_xplus = extract_height(textureLodOffset(sampler2DArray(base_heightmaps, linear), hm_texcoord3, 0, ivec2(1,0)).x); 64 | float height_yplus = extract_height(textureLodOffset(sampler2DArray(base_heightmaps, linear), hm_texcoord3, 0, ivec2(0,1)).x); 65 | float spacing = 19545.9832 / float(1 << node.level); 66 | normal = normalize(vec3(height_xplus - height, spacing, height_yplus - height)); 67 | } else if (node.level <= MAX_HEIGHTMAP_LEVEL) { 68 | vec3 h_texcoord3 = layer_to_texcoord(HEIGHTMAPS_LAYER); 69 | height = extract_height(textureLod(sampler2DArray(heightmaps, linear), h_texcoord3, 0).x); 70 | float height_xplus = extract_height(textureLodOffset(sampler2DArray(heightmaps, linear), h_texcoord3, 0, ivec2(1,0)).x); 71 | float height_yplus = extract_height(textureLodOffset(sampler2DArray(heightmaps, linear), h_texcoord3, 0, ivec2(0,1)).x); 72 | float spacing = 19545.9832 / float(1 << node.level); 73 | normal = normalize(vec3(height_xplus - height, spacing, height_yplus - height)); 74 | } else { 75 | const float spacing = 19545.9832 / float(1 << MAX_HEIGHTMAP_LEVEL); 76 | 77 | int upscale_levels = int(node.level - MAX_HEIGHTMAP_LEVEL); 78 | 79 | uvec2 base_uv = uvec2(HEIGHTMAP_BORDER-2) + uvec2((node.coords & ((1<> upscale_levels; 80 | uint index = gl_LocalInvocationID.x * 16 + gl_LocalInvocationID.y; 81 | 82 | // vec2 full_uv = hm_texcoord3.xy * HEIGHTMAP_RESOLUTION; 83 | // base_uv = uvec2(floor((full_uv - vec2(HEIGHTMAP_BORDER)) / 16) * 16 + vec2(HEIGHTMAP_BORDER)); 84 | 85 | for (uint i = index; i < 20*20; i += 256){ 86 | uvec2 uv = uvec2(i%20, i/20); 87 | heights[uv.x][uv.y] = extract_height(texelFetch(heightmaps, 88 | ivec3(base_uv + uv, node.layers[HEIGHTMAPS_LAYER].slot), 0).x); 89 | } 90 | barrier(); 91 | 92 | for (uint i = index; i < 18*18; i += 256) { 93 | uint x = i%18; 94 | uint y = i/18; 95 | slopes[x+1][y+1] = vec3( 96 | heights[x+1][y] - heights[x-1][y], 97 | heights[x][y+1] - heights[x][y-1], 98 | heights[x+1][y+1] - heights[x-1][y] - heights[x][y-1] + heights[x][y]) 99 | * vec3(0.5); 100 | } 101 | barrier(); 102 | 103 | vec2 full_uv = vec2(HEIGHTMAP_BORDER) + vec2((node.coords & ((1<= 0) { 157 | float waterlevel_value = extract_height(textureLod(sampler2DArray(waterlevel, linear), layer_to_texcoord(WATERLEVEL_LAYER), 0).x); 158 | water_amount = smoothstep(waterlevel_value, waterlevel_value - 1.5, height); 159 | } 160 | if (water_amount > 0.5) 161 | normal = vec3(0,1,0); 162 | // if (!is_water) { 163 | // float spacing = 19545.9832 / float(1 << node.level); 164 | // normal = vec3(h10 + h11 - h00 - h01, 165 | // 2.0 * spacing, 166 | // -1.0 * (h01 + h11 - h00 - h10)); 167 | // normal = normalize(normal); 168 | // } 169 | 170 | vec4 noise_value = vec4(0.5);//texture(sampler2D(noise, linear_wrap), vec2(world_pos.xy*.0001)); 171 | 172 | vec4 albedo_roughness = vec4(.011, .03, .003, 0.7); 173 | float rock = 1-smoothstep(0.80, .95, normal.y); 174 | 175 | float grass_fraction = mix(0, .3, smoothstep(0.95, 1, normal.y)); 176 | float grass = step(grass_fraction, dot(noise_value, vec4(.25))); 177 | albedo_roughness.rgb = mix(vec3(.03,.02,0), vec3(0,.1,0), grass); 178 | 179 | albedo_roughness.rgb = mix(albedo_roughness.rgb, vec3(0.02), rock); 180 | 181 | // if (ubo.parent_slot >= 0) { 182 | // vec2 nv = guassian_random(gl_GlobalInvocationID.xy); 183 | // ivec2 offset = clamp(ivec2(round(nv)), ivec2(-1), ivec2(1)); 184 | // vec4 p = texelFetch(albedo_in, ivec2(ubo.parent_origin + (out_pos+offset)/2), 0); 185 | 186 | // // HACK: We want to avoid blending in water texels onto the land. Since those texels are 187 | // // known to have low roughness, we can filter them with this check. If the lookup fails, 188 | // // we use albedo and roughness values for sand. 189 | // if (p.a > 0.5) { 190 | // albedo_roughness = p; 191 | // } else { 192 | // albedo_roughness = vec4(.2, .2, .15, .8); 193 | // } 194 | // } 195 | 196 | int lod = clamp(22 - int(node.level), 0, 10); 197 | uvec2 v = ((node.coords%uvec2(128)) * 512 + uvec2(gl_GlobalInvocationID.xy)) % uvec2(1024 >> lod); 198 | vec3 v1 = texelFetch(ground_albedo, ivec3(v,0), lod).rgb; 199 | vec3 v2 = texelFetch(ground_albedo, ivec3(v,1), lod).rgb; 200 | vec3 v3 = texelFetch(ground_albedo, ivec3(v,2), lod).rgb; // rock 201 | 202 | if (smoothstep(2000, 3000, height) > 1 - normal.y && false) 203 | albedo_roughness = vec4(v3, 0.8); 204 | else if (height < 2) 205 | albedo_roughness = vec4(.2, .2, .15, .8); 206 | else if (normal.y < 0.95 + 0.03 * noise_value.w) 207 | albedo_roughness = vec4(vec3(0.06), 0.8); 208 | else { 209 | float g = smoothstep(0.97, 0.99, normal.y + 0.02 * noise_value.w) * smoothstep(90, 100, height); 210 | albedo_roughness = vec4(mix(v1, v2, g), .8); 211 | } 212 | 213 | albedo_roughness.rgb = mix(balbedo, albedo_roughness.rgb, 0.25); 214 | 215 | // if (water_amount > 0.5) { 216 | // albedo_roughness.a = 0.2; 217 | // albedo_roughness.rgb = vec3(0,0,0.5); 218 | // // float negative_depth = min(h00 + h10 + h01 + h11, 0); 219 | // // albedo_roughness.rgb = mix(vec3(0,.03,.2), albedo_roughness.rgb, exp(negative_depth * vec3(5,.5,.5))); 220 | // } 221 | 222 | if (node.level < 13) { 223 | float treecover_value = textureLod(sampler2DArray(treecover, linear), layer_to_texcoord(TREECOVER_LAYER), 0).r; 224 | // if (node.layers[TREE_ATTRIBUTES_LAYER].slot >= 0) { 225 | // vec3 tcoord = layer_to_texcoord(TREE_ATTRIBUTES_LAYER); 226 | // vec4 tree_attr = textureLod(sampler2DArray(tree_attributes, nearest), tcoord+vec3(0.5,0.5,0)/516, 0); 227 | 228 | // // vec2 topdown_tcoord = (fract(tcoord.xy*516-0.5)-0.5)*0.4 + 0.5; 229 | // // vec4 tree_albedo = textureLod(sampler2D(topdown_albedo, linear), topdown_tcoord, 0); 230 | 231 | // float tree_amount = tree_attr.a > 0 ? 1 : 0; 232 | // albedo_roughness = mix(albedo_roughness, vec4(0.035,0.045,0.0,0.4), tree_amount); 233 | // normal = normalize(mix(normal, vec3(0,1,0),tree_amount)); 234 | // } else { 235 | normal = normalize(mix(normal, vec3(0,1,0),treecover_value)); 236 | albedo_roughness = mix(albedo_roughness, vec4(0.035,0.045,0.0,0.4), min(treecover_value, 1)); 237 | // } 238 | } 239 | 240 | // if (node.level > 8) 241 | // water_amount = step(height, 0); 242 | 243 | albedo_roughness = mix(albedo_roughness, vec4(.01, .03, .05, .2), water_amount); 244 | 245 | imageStore(normals, ivec3(gl_GlobalInvocationID.xy, node.layers[NORMALS_LAYER].slot), vec4(normal.xz*0.5+0.5, 0.0, 0.0)); 246 | imageStore(albedo, ivec3(gl_GlobalInvocationID.xy, node.layers[ALBEDO_LAYER].slot), albedo_roughness); 247 | } 248 | -------------------------------------------------------------------------------- /src/shaders/gen-root-aerial-perspective.comp: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | 4 | layout(local_size_x = 8, local_size_y = 8) in; 5 | 6 | layout(set = 0, binding = 0, std140) uniform GlobalsBlock { 7 | Globals globals; 8 | }; 9 | layout(set = 0, binding = 1, std140) readonly buffer Nodes { 10 | Node nodes[]; 11 | }; 12 | layout(binding = 2, std430) readonly buffer UniformBlock { 13 | uint node_list[1024]; 14 | } ubo; 15 | 16 | layout(set = 0, binding = 3) uniform sampler nearest; 17 | layout(set = 0, binding = 4) uniform texture2DArray displacements; 18 | layout(set = 0, binding = 5) uniform texture2D transmittance; 19 | layout(rgba16f, binding = 6) writeonly uniform image2DArray root_aerial_perspective; 20 | 21 | #include "atmosphere.glsl" 22 | 23 | const vec3 ellipsoid_to_sphere = vec3(1, 1, 1.0033640898210048); 24 | 25 | void main() { 26 | uint slot = ubo.node_list[gl_GlobalInvocationID.z]; 27 | Node node = nodes[slot]; 28 | 29 | ivec2 iPosition = ivec2(gl_GlobalInvocationID.xy); 30 | vec3 texcoord = layer_texcoord(node.layers[DISPLACEMENTS_LAYER], vec2(iPosition) / 64.0); 31 | vec3 position = textureLod(sampler2DArray(displacements, nearest), texcoord, 0).xyz 32 | - nodes[node.layers[DISPLACEMENTS_LAYER].slot].relative_position; 33 | 34 | vec3 x0 = globals.camera * ellipsoid_to_sphere; 35 | vec3 x1 = (globals.camera + position) * ellipsoid_to_sphere; 36 | vec3 r = normalize(x1 - x0); 37 | vec2 p = rsi(x0, r, atmosphereRadius); 38 | 39 | vec4 output_value = vec4(0, 0, 0, 1); 40 | if (p.x < p.y && p.y >= 0) { 41 | x0 += r * max(p.x, 0.0); 42 | output_value.a = precomputed_transmittance2(x1, x0).b; 43 | output_value.rgb = atmosphere(x0, x1, globals.sun_direction) * vec3(1.0 / 16.0); 44 | } 45 | 46 | imageStore(root_aerial_perspective, ivec3(gl_GlobalInvocationID.xy, slot), output_value); 47 | } 48 | -------------------------------------------------------------------------------- /src/shaders/gen-skyview.comp: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | 4 | layout(local_size_x = 8, local_size_y = 8) in; 5 | 6 | layout(set = 0, binding = 0, std140) uniform GlobalsBlock { 7 | Globals globals; 8 | }; 9 | layout(set = 0, binding = 1) uniform sampler nearest; 10 | layout(set = 0, binding = 2) uniform texture2D transmittance; 11 | layout(rgba16f, binding = 3) writeonly uniform image2D skyview; 12 | 13 | #include "atmosphere.glsl" 14 | 15 | const ivec2 SKY_VIEW_DIMENSIONS = ivec2(128, 128); 16 | 17 | const float PI = 3.1415926535; 18 | const vec3 ellipsoid_to_sphere = vec3(1, 1, 1.0033640898210048); 19 | 20 | void main() { 21 | vec3 camera = normalize(globals.camera * ellipsoid_to_sphere); 22 | vec3 sun = normalize(globals.sun_direction); 23 | vec3 a = normalize(cross(camera, sun)); 24 | vec3 b = normalize(cross(camera, a)); 25 | 26 | vec2 uv = vec2(gl_GlobalInvocationID.xy) / (SKY_VIEW_DIMENSIONS - 1); 27 | uv.x = uv.x * uv.x; 28 | 29 | float camera_distance = length(globals.camera * ellipsoid_to_sphere); 30 | float min_theta = -PI/2 + asin(planetRadius / camera_distance); 31 | float max_theta = camera_distance < atmosphereRadius ? PI/2 : -PI/2 + asin(atmosphereRadius / camera_distance); 32 | 33 | float theta = mix(min_theta, max_theta, uv.x); 34 | float phi = mix(-PI, PI, uv.y); 35 | 36 | vec3 r = camera * sin(theta) + (a * cos(phi) + b * sin(phi)) * cos(theta); 37 | 38 | vec3 x0 = globals.camera * ellipsoid_to_sphere; 39 | vec2 p = rsi(x0, r, atmosphereRadius); 40 | 41 | vec4 output_value = vec4(0, 0, 0, 1); 42 | if (p.x < p.y && p.y > 0.0) { 43 | vec3 x1 = x0 + r * p.y; 44 | x0 = x0 + r * max(p.x, 0.0); 45 | 46 | output_value.a = precomputed_transmittance2(x1, x0).b; 47 | output_value.rgb = atmosphere(x0, x1, globals.sun_direction); 48 | } 49 | output_value *= vec4(1.0 / 16.0); 50 | 51 | if (gl_GlobalInvocationID.x == SKY_VIEW_DIMENSIONS.x - 1) 52 | output_value = vec4(0, 0, 0, 1); 53 | 54 | imageStore(skyview, ivec2(gl_GlobalInvocationID.xy), output_value); 55 | } 56 | -------------------------------------------------------------------------------- /src/shaders/gen-terrain-bounding.comp: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | 4 | layout(local_size_x = 33) in; 5 | 6 | struct Sphere { 7 | vec3 center; 8 | float radius; 9 | }; 10 | 11 | layout(std140, binding = 0) uniform UniformBlock { 12 | GenMeshUniforms ubo; 13 | }; 14 | layout(std430, binding = 1) /*writeonly*/ buffer BoundingBlock { 15 | Sphere bounds[]; 16 | } mesh_bounding; 17 | layout(set = 0, binding = 2) uniform texture2DArray displacements; 18 | layout(set = 0, binding = 3) uniform sampler nearest; 19 | 20 | shared float max_radius2[33]; 21 | 22 | void main() { 23 | uint mesh_slot = ubo.mesh_base_entry + gl_WorkGroupID.x; 24 | 25 | ivec2 origin = ivec2(gl_WorkGroupID.x % 2, gl_WorkGroupID.x / 2) * 32; 26 | vec3 center = textureLod(sampler2DArray(displacements, nearest), vec3((origin.x + 16+0.5) / 65.0, (origin.y+16+0.5) / 65.0, ubo.slot), 0).xyz; 27 | 28 | max_radius2[gl_LocalInvocationID.x] = 0.0; 29 | for (int i = 0; i < 33; i++) { 30 | vec3 p = textureLod(sampler2DArray(displacements, nearest), vec3((origin.x + i+0.5) / 65.0, (origin.y + gl_LocalInvocationID.x+0.5) / 65.0, ubo.slot), 0).xyz; 31 | vec3 v = p - center; 32 | max_radius2[gl_LocalInvocationID.x] = max(max_radius2[gl_LocalInvocationID.x], dot(v, v)); 33 | } 34 | 35 | barrier(); 36 | 37 | if (gl_LocalInvocationID.x == 0) { 38 | float m = 0.0; 39 | for (int i = 0; i < 33; i++) { 40 | m = max(m, max_radius2[i]); 41 | } 42 | 43 | mesh_bounding.bounds[mesh_slot].center = center; 44 | mesh_bounding.bounds[mesh_slot].radius = sqrt(m); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/shaders/gen-transmittance.comp: -------------------------------------------------------------------------------- 1 | #line 2 2 | 3 | layout(local_size_x = 8, local_size_y = 8) in; 4 | 5 | layout(binding = 0) uniform UniformBlock { 6 | float padding; 7 | } ubo; 8 | layout(rgba16f, binding = 1) uniform image2D transmittance; 9 | 10 | const float Rg = 6371000.0; 11 | const float Rt = 6471000.0; 12 | 13 | void compute_parameters(ivec2 size, float u_r, float u_mu, out float r, out float mu) { 14 | float H = sqrt(Rt * Rt - Rg * Rg); 15 | float rho = u_r * H; 16 | r = sqrt(rho * rho + Rg * Rg); 17 | 18 | float hp = (size.y / 2 - 1) / (size.y - 1); 19 | float mu_horizon = -sqrt(r * r - Rg * Rg) / r; 20 | if (u_mu > 0.5) { 21 | float uu = (u_mu - (1.0 - hp)) / hp; 22 | mu = pow(uu, 5.0) * (1.0 - mu_horizon) + mu_horizon; 23 | } else { 24 | float uu = u_mu / hp; 25 | mu = -pow(uu, 5.0) * (1.0 + mu_horizon) + mu_horizon; 26 | } 27 | } 28 | 29 | void main() { 30 | const int steps = 16; 31 | 32 | vec3 rayleigh_Beta_e = vec3(5.8e-6, 13.5e-6, 33.1e-6); 33 | vec3 rayleigh_Beta_s = rayleigh_Beta_e; 34 | float rayleigh_H = 8000.0; 35 | 36 | float mie_Beta_s = 2.0e-6; 37 | float mie_Beta_e = mie_Beta_s / 0.9; 38 | float mie_H = 1200.0; 39 | float mie_g = 0.76; 40 | 41 | float r, v; 42 | ivec2 tsize = imageSize(transmittance).xy; 43 | compute_parameters(tsize, 44 | float(gl_GlobalInvocationID.x) / (tsize.x - 1), 45 | float(gl_GlobalInvocationID.y) / (tsize.y - 1), 46 | r, v); 47 | 48 | float theta = acos(v); 49 | float b = 2.0 * r * cos(theta); 50 | float c_atmosphere = r * r - Rt * Rt; 51 | float c_ground = r * r - Rg * Rg; 52 | 53 | float len = 0; 54 | if (gl_GlobalInvocationID.y < tsize.y / 2) { // force hit against planet surface 55 | if (b * b - 4.0 * c_ground >= 0.0) { 56 | len = (-b - sqrt(b * b - 4.0 * c_ground)) / 2.0; 57 | } else { 58 | // Doesn't actually hit planet surface. Fake it by taking closest point. 59 | len = -b / 2.0; 60 | } 61 | } else { 62 | len = (-b + sqrt(b * b - 4.0 * c_atmosphere)) / 2.0; 63 | }; 64 | 65 | vec3 t = vec3(0); 66 | if (len > 0.0) { 67 | float step_length = len / steps; 68 | vec2 x = vec2(0.0, r); 69 | vec2 v = vec2(sin(theta), cos(theta)) * step_length; 70 | for (int i = 0; i < steps; i++) { 71 | vec2 y = x + v * (i + 0.5); 72 | float height = length(y) - Rg; 73 | vec3 Beta_e_R = rayleigh_Beta_e * exp(-height / rayleigh_H); 74 | float Beta_e_M = mie_Beta_e * exp(-height / mie_H); 75 | t = t + (Beta_e_R + vec3(Beta_e_M, Beta_e_M, Beta_e_M)) * step_length; 76 | } 77 | } 78 | 79 | vec4 result = vec4(exp(-t.x), exp(-t.y), exp(-t.z), 0.0); 80 | imageStore(transmittance, ivec2(gl_GlobalInvocationID.xy), result); 81 | } 82 | -------------------------------------------------------------------------------- /src/shaders/gen-tree-attributes.comp: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | #include "hash.glsl" 4 | 5 | layout(local_size_x = 8, local_size_y = 8) in; 6 | 7 | layout(set = 0, binding = 0, std140) readonly buffer Nodes { 8 | Node nodes[]; 9 | }; 10 | layout(binding = 1) readonly buffer UniformBlock { 11 | int slots[]; 12 | } ubo; 13 | 14 | layout(binding = 2) uniform texture2DArray treecover; 15 | layout(binding = 3) uniform sampler linear; 16 | layout(binding = 4) uniform texture2DArray heightmaps; 17 | layout(binding = 5) uniform texture2DArray waterlevel; 18 | 19 | layout(rgba8, binding = 6) writeonly uniform image2DArray tree_attributes; 20 | 21 | void main() { 22 | Node node = nodes[ubo.slots[gl_GlobalInvocationID.z]]; 23 | 24 | vec2 texcoord = vec2(gl_GlobalInvocationID.xy-1.5) / vec2(512); 25 | vec3 texcoord3 = layer_texcoord(node.layers[TREECOVER_LAYER], texcoord); 26 | float coverage = textureLod(sampler2DArray(treecover, linear), texcoord3, 0).r; 27 | 28 | float height = extract_height(textureLod(sampler2DArray(heightmaps, linear), layer_texcoord(node.layers[HEIGHTMAPS_LAYER], texcoord), 0).x); 29 | float water_surface = extract_height(textureLod(sampler2DArray(waterlevel, linear), layer_texcoord(node.layers[WATERLEVEL_LAYER], texcoord),0).x); 30 | 31 | vec4 output_value = vec4(0); 32 | if (random(gl_GlobalInvocationID.xy) < coverage && height > water_surface) { 33 | float x = random(uvec3(gl_GlobalInvocationID.xy, 1)); 34 | float y = random(uvec3(gl_GlobalInvocationID.xy, 2)); 35 | float seed = random(uvec3(gl_GlobalInvocationID.xy, 3)); 36 | output_value = vec4(x, y, seed, 1 / 255.0); 37 | } 38 | 39 | imageStore(tree_attributes, ivec3(gl_GlobalInvocationID.xy, node.layers[TREE_ATTRIBUTES_LAYER].slot), output_value); 40 | } 41 | -------------------------------------------------------------------------------- /src/shaders/gen-tree-billboards.wgsl: -------------------------------------------------------------------------------- 1 | struct Entry { 2 | position: vec3, 3 | angle: f32, 4 | albedo: vec3, 5 | height: f32, 6 | padding0: vec4, 7 | padding1: vec4, 8 | }; 9 | struct Entries { 10 | entries: array>, 11 | }; 12 | 13 | @group(0) @binding(0) var ubo: GenMeshUniforms; 14 | @group(0) @binding(1) var tree_billboards_storage: Entries; 15 | @group(0) @binding(2) var nodes: Nodes; 16 | @group(0) @binding(3) var mesh_indirect: Indirects; 17 | @group(0) @binding(4) var linearsamp: sampler; 18 | @group(0) @binding(5) var nearest: sampler; 19 | @group(0) @binding(6) var displacements: texture_2d_array; 20 | @group(0) @binding(7) var tree_attributes: texture_2d_array; 21 | 22 | 23 | @compute 24 | @workgroup_size(8,8) 25 | fn main( 26 | @builtin(global_invocation_id) global_id: vec3, 27 | ) { 28 | let node = nodes.entries[ubo.slot]; 29 | 30 | let index = global_id.xy % vec2(32u); 31 | let entry = 4u * (global_id.y / 32u) + (global_id.x / 32u); 32 | 33 | let rnd1 = random3(vec3(vec2(index), 1.0)); 34 | let rnd2 = random3(vec3(vec2(index), 2.0)); 35 | let rnd3 = random3(vec3(vec2(index), 3.0)); 36 | let rnd4 = random3(vec3(vec2(index), 4.0)); 37 | let rnd5 = random3(vec3(vec2(index), 5.0)); 38 | 39 | let tree_attr = textureSampleLevel( 40 | tree_attributes, 41 | nearest, 42 | layer_texcoord(node.layers[TREE_ATTRIBUTES_LAYER], vec2(global_id.xy) / 128.0), 43 | node.layers[TREE_ATTRIBUTES_LAYER].slot, 44 | 0.0 45 | ); 46 | 47 | if (tree_attr.a == 0.0) { 48 | return; 49 | } 50 | 51 | // Sample displacements texture at random offset (rnd1, rnd). 52 | let texcoord = layer_texcoord(node.layers[DISPLACEMENTS_LAYER], (vec2(global_id.xy) + vec2(rnd1, rnd2)) / 128.0); 53 | let array_index = node.layers[DISPLACEMENTS_LAYER].slot; 54 | let dimensions = textureDimensions(displacements); 55 | let stexcoord = max(texcoord.xy * vec2(dimensions) - vec2(0.5), vec2(0.0)); 56 | let f = fract(stexcoord); 57 | let base_coords = vec2(stexcoord - f); 58 | let i00 = textureLoad(displacements, base_coords, array_index, 0); 59 | let i10 = textureLoad(displacements, min(base_coords + vec2(1,0), dimensions-vec2(1)), array_index, 0); 60 | let i01 = textureLoad(displacements, min(base_coords + vec2(0,1), dimensions-vec2(1)), array_index, 0); 61 | let i11 = textureLoad(displacements, min(base_coords + vec2(1,1), dimensions-vec2(1)), array_index, 0); 62 | let position = mix(mix(i00, i10, f.x), mix(i01, i11, f.x), f.y); 63 | 64 | let i = atomicAdd(&mesh_indirect.entries[ubo.mesh_base_entry + entry].vertex_count, 6) / 6; 65 | tree_billboards_storage.entries[ubo.storage_base_entry + entry][i].position = position.xyz; 66 | tree_billboards_storage.entries[ubo.storage_base_entry + entry][i].albedo = vec3(rnd3, rnd4, rnd5); 67 | tree_billboards_storage.entries[ubo.storage_base_entry + entry][i].angle = 0.0; 68 | tree_billboards_storage.entries[ubo.storage_base_entry + entry][i].height = 10.0; 69 | } 70 | -------------------------------------------------------------------------------- /src/shaders/grass.frag: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | #include "pbr.glsl" 4 | 5 | layout(early_fragment_tests) in; 6 | 7 | layout(set = 0, binding = 0) uniform UniformBlock { 8 | Globals globals; 9 | }; 10 | layout(set = 0, binding = 8, std140) readonly buffer Nodes { 11 | Node nodes[]; 12 | }; 13 | 14 | // layout(set = 0, binding = 1, std140) uniform NodeBlock { 15 | // vec3 relative_position; 16 | // float min_distance; 17 | // vec3 parent_relative_position; 18 | // float padding1; 19 | 20 | // uint slot; 21 | // uvec3 padding2; 22 | // } node; 23 | 24 | // layout(set = 0, binding = 3) uniform sampler linear; 25 | // layout(set = 0, binding = 4) uniform texture2DArray normals; 26 | // layout(set = 0, binding = 5) uniform texture2DArray albedo; 27 | // layout(set = 0, binding = 6) uniform texture2DArray roughness; 28 | 29 | 30 | layout(location = 0) in vec3 position; 31 | layout(location = 1) in vec3 color; 32 | layout(location = 2) in vec2 texcoord; 33 | layout(location = 3) in vec3 normal; 34 | // layout(location = 4) flat in uint instance; 35 | 36 | layout(location = 0) out vec4 out_color; 37 | 38 | vec3 extract_normal(vec2 n) { 39 | n = n * 2.0 - vec2(1.0); 40 | float y = sqrt(max(1.0 - dot(n, n),0)); 41 | return normalize(vec3(n.x, y, n.y)); 42 | } 43 | 44 | void main() { 45 | out_color = vec4(color, 1); 46 | 47 | // vec3 albedo_value = texture(sampler2DArray(albedo, linear), vec3(texcoord, node.nodes_slot)).xyz; 48 | // vec3 snormal = extract_normal(texture(sampler2DArray(normals, linear), layer_to_texcoord(NORMALS_LAYER)).xy); 49 | float roughness_value = 0.5; 50 | 51 | out_color = vec4(1); 52 | out_color.rgb = pbr(color, 53 | roughness_value, 54 | position, 55 | normal, 56 | globals.camera, 57 | globals.sun_direction, 58 | vec3(100000.0)); 59 | 60 | out_color.rgb += pbr(color, 61 | roughness_value, 62 | position, 63 | -normal, 64 | globals.camera, 65 | globals.sun_direction, 66 | vec3(100000.0)); 67 | 68 | // out_color.rgb = out_color.rgb * 0.3 + 0.7 * pbr(color, 69 | // roughness_value, 70 | // position, 71 | // snormal, 72 | // globals.camera, 73 | // normalize(vec3(0.4, .7, 0.2)), 74 | // vec3(100000.0)); 75 | 76 | out_color = tonemap(out_color, globals.exposure, 2.2); 77 | } -------------------------------------------------------------------------------- /src/shaders/grass.vert: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | 4 | layout(set = 0, binding = 0, std140) uniform UniformBlock { 5 | Globals globals; 6 | }; 7 | 8 | layout(set = 0, binding = 8, std140) readonly buffer Nodes { 9 | Node nodes[]; 10 | }; 11 | 12 | struct Entry { 13 | vec3 position; 14 | float angle; 15 | vec3 albedo; 16 | float slant; 17 | vec2 texcoord; 18 | vec2 _padding1; 19 | vec4 _padding2; 20 | }; 21 | layout(std430, binding = 2) readonly buffer DataBlock { 22 | Entry entries[]; 23 | } grass_storage; 24 | 25 | layout(set = 0, binding = 3) uniform sampler linear; 26 | // layout(set = 0, binding = 9) uniform texture2DArray displacements; 27 | 28 | layout(location = 0) out vec3 position; 29 | layout(location = 1) out vec3 color; 30 | layout(location = 2) out vec2 texcoord; 31 | layout(location = 3) out vec3 normal; 32 | 33 | const vec3 tangents[6] = vec3[6]( 34 | vec3(0,1,0), 35 | vec3(0,-1,0), 36 | vec3(1,0,0), 37 | vec3(-1,0,0), 38 | vec3(1,0,0), 39 | vec3(-1,0,0) 40 | ); 41 | 42 | void main() { 43 | uint entry_index = gl_VertexIndex / 7; 44 | uint index = gl_VertexIndex % 7; 45 | uint slot = gl_InstanceIndex / 16; 46 | 47 | Node node = nodes[slot]; 48 | Entry entry = grass_storage.entries[((slot - GRASS_BASE_SLOT) * 16 + gl_InstanceIndex % 16) * 1024 + entry_index]; 49 | vec3 pos = entry.position - node.relative_position; 50 | 51 | vec3 up = normalize(pos + globals.camera); 52 | vec3 bitangent = normalize(cross(up, tangents[node.face])); 53 | vec3 tangent = normalize(cross(up, bitangent)); 54 | 55 | float morph = 1 - smoothstep(0.7, .99, length(pos) / node.min_distance); 56 | 57 | vec3 offset; 58 | float width = 0.01; 59 | float height = 0.1; 60 | 61 | if (node.min_distance > 24) { 62 | width *= mix(1, 1.5, smoothstep(0.7, .99, 4 * length(pos) / node.min_distance)); 63 | width *= mix(1, 1.5, smoothstep(0.7, .99, 2 * length(pos) / node.min_distance)); 64 | 65 | height *= mix(1, 1.5, smoothstep(0.7, .99, 4 * length(pos) / node.min_distance)); 66 | height *= mix(1, 1.5, smoothstep(0.7, .99, 2 * length(pos) / node.min_distance)); 67 | //morph *= smoothstep(0.7, .99, 2 * length(pos) / node.min_distance); 68 | } else if (node.min_distance > 12) { 69 | width *= mix(1, 1.5, smoothstep(0.7, .99, 2 * length(pos) / node.min_distance)); 70 | height *= mix(1, 1.5, smoothstep(0.7, .99, 2 * length(pos) / node.min_distance)); 71 | } 72 | 73 | vec2 uv = vec2(0); 74 | if (index == 0) uv = vec2(-1, 0); 75 | else if (index == 1) uv = vec2(1, 0); 76 | else if (index == 2) uv = vec2(-.9, .3); 77 | else if (index == 3) uv = vec2(.9, .3); 78 | else if (index == 4) uv = vec2(-.7, .6); 79 | else if (index == 5) uv = vec2(.7, .6); 80 | else if (index == 6) uv = vec2(0, 1); 81 | 82 | vec3 u = cos(entry.angle) * tangent + sin(entry.angle) * bitangent; 83 | vec3 w = -sin(entry.angle) * tangent + cos(entry.angle) * bitangent; 84 | 85 | position = pos + (u*width*uv.x + (up + w*uv.y*entry.slant)*height*uv.y) * morph; 86 | color = mix(entry.albedo, vec3(0, .4, .01), .0*uv.y); 87 | texcoord = entry.texcoord; 88 | normal = up;//normalize(w + up); 89 | 90 | gl_Position = globals.view_proj * vec4(position, 1.0); 91 | } -------------------------------------------------------------------------------- /src/shaders/hash.glsl: -------------------------------------------------------------------------------- 1 | // Collection of hash related functions, courtesy of [Spatial](https://stackoverflow.com/a/17479300) 2 | 3 | // A single iteration of Bob Jenkins' One-At-A-Time hashing algorithm. 4 | uint hash( uint x ) { 5 | x += ( x << 10u ); 6 | x ^= ( x >> 6u ); 7 | x += ( x << 3u ); 8 | x ^= ( x >> 11u ); 9 | x += ( x << 15u ); 10 | return x; 11 | } 12 | 13 | // Compound versions of the hashing algorithm I whipped together. 14 | uint hash( uvec2 v ) { return hash( v.x ^ hash(v.y) ); } 15 | uint hash( uvec3 v ) { return hash( v.x ^ hash(v.y) ^ hash(v.z) ); } 16 | uint hash( uvec4 v ) { return hash( v.x ^ hash(v.y) ^ hash(v.z) ^ hash(v.w) ); } 17 | 18 | // Construct a float with half-open range [0:1] using low 23 bits. 19 | // All zeroes yields 0.0, all ones yields the next smallest representable value below 1.0. 20 | float floatConstruct( uint m ) { 21 | const uint ieeeMantissa = 0x007FFFFFu; // binary32 mantissa bitmask 22 | const uint ieeeOne = 0x3F800000u; // 1.0 in IEEE binary32 23 | 24 | m &= ieeeMantissa; // Keep only mantissa bits (fractional part) 25 | m |= ieeeOne; // Add fractional part to 1.0 26 | 27 | float f = uintBitsToFloat( m ); // Range [1:2] 28 | return f - 1.0; // Range [0:1] 29 | } 30 | 31 | // Pseudo-random value in half-open range [0:1]. 32 | float random( float x ) { return floatConstruct(hash(floatBitsToUint(x))); } 33 | float random( vec2 v ) { return floatConstruct(hash(floatBitsToUint(v))); } 34 | float random( vec3 v ) { return floatConstruct(hash(floatBitsToUint(v))); } 35 | float random( vec4 v ) { return floatConstruct(hash(floatBitsToUint(v))); } 36 | 37 | float random(uint v) { return floatConstruct(hash(v)); } 38 | float random(uvec2 v) { return floatConstruct(hash(v)); } 39 | float random(uvec3 v) { return floatConstruct(hash(v)); } 40 | float random(uvec4 v) { return floatConstruct(hash(v)); } 41 | 42 | vec2 box_muller_transform(float u1, float u2) { 43 | float r = sqrt(-2.0 * log(1-u1)); 44 | float theta = 2.0*3.14159265*u2; 45 | return vec2(r*sin(theta), r*cos(theta)); 46 | } 47 | 48 | vec2 guassian_random(float v) { 49 | float r = random(v); 50 | return box_muller_transform(r, random(r)); 51 | } 52 | vec2 guassian_random(vec2 v) { 53 | float r = random(v); 54 | return box_muller_transform(r, random(r)); 55 | } 56 | vec2 guassian_random(vec3 v) { 57 | float r = random(v); 58 | return box_muller_transform(r, random(r)); 59 | } 60 | 61 | vec3 dither(vec2 fragcoord) { 62 | return vec3(random(fragcoord) - 0.5, 63 | random(fragcoord + vec2(1.2345, 6.7890)) - 0.5, 64 | random(fragcoord + vec2(6.7890, 1.2345)) - 0.5) 65 | * 0.00392156862; 66 | } 67 | -------------------------------------------------------------------------------- /src/shaders/model.frag: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | 4 | layout(early_fragment_tests) in; 5 | 6 | layout(location = 0) in vec4 position; 7 | layout(location = 1) in vec2 texcoord; 8 | layout(location = 2) in vec3 normal; 9 | layout(location = 3) in float ao; 10 | layout(location = 4) in vec4 view_0; 11 | layout(location = 5) in vec4 view_1; 12 | layout(location = 6) in vec4 view_2; 13 | 14 | layout(location = 0) out vec4 out_color; 15 | layout(location = 1) out vec4 out_normals; 16 | layout(location = 2) out float out_depth; 17 | layout(location = 3) out float out_ao; 18 | 19 | layout(set = 0, binding = 1) uniform sampler linear_wrap; 20 | layout(set = 0, binding = 2) uniform texture2D models_albedo; 21 | 22 | void main() { 23 | mat4 view; 24 | view[0] = view_0; 25 | view[1] = view_1; 26 | view[2] = view_2; 27 | view[3] = vec4(0.0, 0.0, 0.0, 1.0); 28 | 29 | out_color = texture(sampler2D(models_albedo, linear_wrap), texcoord); 30 | if (out_color.a < 1 || gl_FragCoord.z == 0) 31 | discard; 32 | out_color.a = 1;//out_color.a > 0.5 ? 1 : 0; 33 | 34 | out_normals = vec4(normalize((view * vec4(normal,0)).xyz).xyz, 0); 35 | out_depth = gl_FragCoord.z;//position.z * 5; 36 | out_ao = 1 - ao; 37 | } 38 | -------------------------------------------------------------------------------- /src/shaders/model.vert: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | 4 | struct Vertex { 5 | vec3 position; 6 | float ao; 7 | vec3 lod_position; 8 | uint color; 9 | vec3 normal; 10 | float texcoord_u; 11 | vec3 binormal; 12 | float texcoord_v; 13 | }; 14 | layout(std430, binding = 0) readonly buffer DataBlock { 15 | Vertex vertices[]; 16 | } model_storage; 17 | 18 | layout(push_constant) uniform constants { 19 | float x; 20 | float z; 21 | } push_constants; 22 | 23 | layout(location = 0) out vec4 position; 24 | layout(location = 1) out vec2 texcoord; 25 | layout(location = 2) out vec3 normal; 26 | layout(location = 3) out float ao; 27 | layout(location = 4) out vec4 view_0; 28 | layout(location = 5) out vec4 view_1; 29 | layout(location = 6) out vec4 view_2; 30 | 31 | void main() { 32 | Vertex vertex = model_storage.vertices[gl_VertexIndex]; 33 | 34 | float y = 1.0 - max(abs(push_constants.x), abs(push_constants.z)); 35 | 36 | vec3 up = vec3(0,1,0); 37 | vec3 f = normalize(vec3(push_constants.x, y, push_constants.z)); 38 | 39 | if (y > 0.999) 40 | up = vec3(0, 0, 1); 41 | 42 | vec3 s = normalize(cross(f, up)); 43 | vec3 u = cross(s, f); 44 | 45 | float size = 9; 46 | vec3 eye = vec3(0, size, 0) - f * 10; 47 | mat4 proj = mat4(1./size, 0, 0, 0, 48 | 0, 1./size, 0, 0, 49 | 0, 0, -0.5/size, 0, 50 | 0, 0, 0, 1); 51 | 52 | mat4 view = mat4( 53 | s.x, u.x, -f.x, 0, 54 | s.y, u.y, -f.y, 0, 55 | s.z, u.z, -f.z, 0, 56 | -dot(eye, s), -dot(eye, u), dot(eye, f), 1 57 | ); 58 | 59 | position = proj * view * vec4(vertex.position, 1); 60 | //position = vec4(vertex.position * 0.05, 1); 61 | 62 | texcoord = vec2(vertex.texcoord_u, 1-vertex.texcoord_v); 63 | normal = normalize((view * vec4(vertex.normal,0)).xyz); 64 | ao = vertex.ao; 65 | 66 | view_0 = view[0]; 67 | view_1 = view[1]; 68 | view_2 = view[2]; 69 | 70 | // position.z = 0.5; 71 | // position.y -= 1.0; 72 | 73 | gl_Position = position; 74 | } -------------------------------------------------------------------------------- /src/shaders/pbr.glsl: -------------------------------------------------------------------------------- 1 | // From: https://github.com/SaschaWillems/Vulkan-glTF-PBR/blob/master/data/shaders/pbr_khr.frag 2 | 3 | struct PBRInfo 4 | { 5 | float NdotL; // cos angle between normal and light direction 6 | float NdotV; // cos angle between normal and view direction 7 | float NdotH; // cos angle between normal and half vector 8 | float LdotH; // cos angle between light direction and half vector 9 | float VdotH; // cos angle between view direction and half vector 10 | float perceptualRoughness; // roughness value, as authored by the model creator (input to shader) 11 | float alphaRoughness; // roughness mapped to a more linear change in the roughness (proposed by [2]) 12 | vec3 diffuseColor; // color contribution from diffuse lighting 13 | }; 14 | 15 | const float M_PI = 3.141592653589793; 16 | const float c_MinRoughness = 0.04; 17 | 18 | const float PBR_WORKFLOW_METALLIC_ROUGHNESS = 0.0; 19 | const float PBR_WORKFLOW_SPECULAR_GLOSINESS = 1.0f; 20 | 21 | #define MANUAL_SRGB 1 22 | 23 | vec3 Uncharted2Tonemap(vec3 color) 24 | { 25 | float A = 0.15; 26 | float B = 0.50; 27 | float C = 0.10; 28 | float D = 0.20; 29 | float E = 0.02; 30 | float F = 0.30; 31 | float W = 11.2; 32 | return ((color*(A*color+C*B)+D*E)/(color*(A*color+B)+D*F))-E/F; 33 | } 34 | 35 | vec4 tonemap(vec4 color, float exposure, float gamma) 36 | { 37 | vec3 outcol = Uncharted2Tonemap(color.rgb * exposure); 38 | outcol = outcol * (1.0f / Uncharted2Tonemap(vec3(11.2f))); 39 | return vec4(pow(outcol, vec3(1.0f / gamma)), color.a); 40 | } 41 | 42 | // vec4 SRGBtoLINEAR(vec4 srgbIn) 43 | // { 44 | // #ifdef MANUAL_SRGB 45 | // #ifdef SRGB_FAST_APPROXIMATION 46 | // vec3 linOut = pow(srgbIn.xyz,vec3(2.2)); 47 | // #else //SRGB_FAST_APPROXIMATION 48 | // vec3 bLess = step(vec3(0.04045),srgbIn.xyz); 49 | // vec3 linOut = mix( srgbIn.xyz/vec3(12.92), pow((srgbIn.xyz+vec3(0.055))/vec3(1.055),vec3(2.4)), bLess ); 50 | // #endif //SRGB_FAST_APPROXIMATION 51 | // return vec4(linOut,srgbIn.w);; 52 | // #else //MANUAL_SRGB 53 | // return srgbIn; 54 | // #endif //MANUAL_SRGB 55 | // } 56 | 57 | // // Calculation of the lighting contribution from an optional Image Based Light source. 58 | // // Precomputed Environment Maps are required uniform inputs and are computed as outlined in [1]. 59 | // // See our README.md on Environment Maps [3] for additional discussion. 60 | // vec3 getIBLContribution(PBRInfo pbrInputs, vec3 n, vec3 reflection) 61 | // { 62 | // float lod = (pbrInputs.perceptualRoughness * uboParams.prefilteredCubeMipLevels); 63 | // // retrieve a scale and bias to F0. See [1], Figure 3 64 | // vec3 brdf = (texture(samplerBRDFLUT, vec2(pbrInputs.NdotV, 1.0 - pbrInputs.perceptualRoughness))).rgb; 65 | // vec3 diffuseLight = SRGBtoLINEAR(tonemap(texture(samplerIrradiance, n))).rgb; 66 | // vec3 specularLight = SRGBtoLINEAR(tonemap(textureLod(prefilteredMap, reflection, lod))).rgb; 67 | // vec3 diffuse = diffuseLight * pbrInputs.diffuseColor; 68 | // vec3 specular = specularLight * (vec3(0.04) * brdf.x + brdf.y); 69 | // return diffuse + specular; 70 | // } 71 | 72 | // Basic Lambertian diffuse. Implementation from Lambert's Photometria 73 | // https://archive.org/details/lambertsphotome00lambgoog See also [1], Equation 74 | // 1 75 | vec3 diffuse(PBRInfo pbrInputs) 76 | { 77 | return pbrInputs.diffuseColor / M_PI; 78 | } 79 | 80 | // The following equation models the Fresnel reflectance term of the spec equation (aka F()) 81 | // Implementation of fresnel from [4], Equation 15 82 | float specularReflection(PBRInfo pbrInputs) 83 | { 84 | return 0.04 + 0.96 * pow(clamp(1.0 - pbrInputs.VdotH, 0.0, 1.0), 5.0); 85 | } 86 | 87 | // This calculates the specular geometric attenuation (aka G()), where rougher 88 | // material will reflect less light back to the viewer. This implementation is 89 | // based on [1] Equation 4, and we adopt their modifications to alphaRoughness 90 | // as input as originally proposed in [2]. 91 | float geometricOcclusion(PBRInfo pbrInputs) 92 | { 93 | float NdotL = pbrInputs.NdotL; 94 | float NdotV = pbrInputs.NdotV; 95 | float r = pbrInputs.alphaRoughness; 96 | 97 | float attenuationL = 2.0 * NdotL / (NdotL + sqrt(r * r + (1.0 - r * r) * (NdotL * NdotL))); 98 | float attenuationV = 2.0 * NdotV / (NdotV + sqrt(r * r + (1.0 - r * r) * (NdotV * NdotV))); 99 | return attenuationL * attenuationV; 100 | } 101 | 102 | // The following equation(s) model the distribution of microfacet normals across 103 | // the area being drawn (aka D()) Implementation from "Average Irregularity 104 | // Representation of a Roughened Surface for Ray Reflection" by 105 | // T. S. Trowbridge, and K. P. Reitz Follows the distribution function 106 | // recommended in the SIGGRAPH 2013 course notes from EPIC Games [1], Equation 107 | // 3. 108 | float microfacetDistribution(PBRInfo pbrInputs) 109 | { 110 | float roughnessSq = pbrInputs.alphaRoughness * pbrInputs.alphaRoughness; 111 | float f = (pbrInputs.NdotH * roughnessSq - pbrInputs.NdotH) * pbrInputs.NdotH + 1.0; 112 | return roughnessSq / (M_PI * f * f); 113 | } 114 | 115 | vec3 pbr(vec3 albedo, 116 | float perceptualRoughness, 117 | vec3 position, 118 | vec3 normal, 119 | vec3 camera, 120 | vec3 lightDir, 121 | vec3 lightColor) { 122 | 123 | vec3 n = normalize(normal); 124 | vec3 v = normalize(camera - position); 125 | vec3 l = normalize(lightDir); 126 | vec3 h = normalize(l+v); 127 | float NdotL = clamp(dot(n, l), 0.0, 1.0); 128 | float NdotV = clamp(abs(dot(n, v)), 0.0, 1.0); 129 | float NdotH = clamp(dot(n, h), 0.0, 1.0); 130 | float LdotH = clamp(dot(l, h), 0.0, 1.0); 131 | float VdotH = clamp(dot(v, h), 0.0, 1.0); 132 | 133 | float alphaRoughness = perceptualRoughness * perceptualRoughness; 134 | 135 | PBRInfo pbrInputs = PBRInfo( 136 | NdotL, 137 | NdotV, 138 | NdotH, 139 | LdotH, 140 | VdotH, 141 | perceptualRoughness, 142 | alphaRoughness, 143 | albedo 144 | ); 145 | 146 | float F = specularReflection(pbrInputs); 147 | float G = geometricOcclusion(pbrInputs); 148 | float D = microfacetDistribution(pbrInputs); 149 | 150 | // Calculation of analytical lighting contribution 151 | vec3 diffuseContrib = (1.0 - F) * diffuse(pbrInputs); 152 | float specContrib = F * G * D / (4.0 * NdotV); 153 | // Obtain final intensity as reflectance (BRDF) scaled by the energy of the light (cosine law) 154 | vec3 color = lightColor * (NdotL * diffuseContrib + vec3(specContrib)); 155 | 156 | // vec3 reflection = -normalize(reflect(v, n)); 157 | // reflection.y *= -1.0f; 158 | // color += getIBLContribution(pbrInputs, n, reflection); 159 | 160 | return color; 161 | } 162 | -------------------------------------------------------------------------------- /src/shaders/shadowpass.frag: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | 3 | void main() {} 4 | -------------------------------------------------------------------------------- /src/shaders/sky.frag: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | #include "pbr.glsl" 4 | #include "hash.glsl" 5 | 6 | layout(set = 0, binding = 0) uniform UniformBlock { 7 | Globals globals; 8 | }; 9 | layout(set = 0, binding = 1) uniform sampler linear; 10 | layout(set = 0, binding = 2) uniform sampler nearest; 11 | layout(set = 0, binding = 3) uniform texture2D sky; 12 | layout(set = 0, binding = 4) uniform texture2D transmittance; 13 | layout(set = 0, binding = 5) uniform texture2D skyview; 14 | 15 | layout(location = 0) in vec4 position; 16 | 17 | layout(location = 0) out vec4 OutColor; 18 | 19 | #include "atmosphere.glsl" 20 | 21 | const float PI = 3.1415926535; 22 | const vec3 ellipsoid_to_sphere = vec3(1, 1, 1.0033640898210048); 23 | 24 | void main() { 25 | vec4 r0 = globals.view_proj_inverse * vec4(position.xy, 1, 1); 26 | vec4 r1 = globals.view_proj_inverse * vec4(position.xy, 1e-9, 1); 27 | vec3 r = normalize(r1.xyz / r1.w - r0.xyz / r0.w); 28 | 29 | vec3 camera = normalize(globals.camera * ellipsoid_to_sphere); 30 | vec3 sun = normalize(globals.sun_direction); 31 | vec3 a = normalize(cross(camera, sun)); 32 | vec3 b = normalize(cross(camera, a)); 33 | 34 | float theta = asin(dot(r, camera)); 35 | float phi = atan(dot(r, b), dot(r, a)) / PI * 0.5 + 0.5; 36 | 37 | float camera_distance = length(globals.camera * ellipsoid_to_sphere); 38 | float min_theta = -PI/2 + asin(planetRadius / camera_distance); 39 | float max_theta = camera_distance < atmosphereRadius ? PI/2 : -PI/2 + asin(atmosphereRadius / camera_distance); 40 | 41 | float u = (theta - min_theta) / (max_theta - min_theta); 42 | u = sqrt(u); 43 | 44 | vec4 sv = texture(sampler2D(skyview, linear), (vec2(u, phi) * 127 + 0.5) / 128); 45 | OutColor.rgb = sv.rgb * 16; 46 | 47 | OutColor = tonemap(OutColor, globals.exposure, 2.2); 48 | OutColor.rgb += dither(gl_FragCoord.xy); 49 | } 50 | -------------------------------------------------------------------------------- /src/shaders/sky.vert: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | 4 | layout(location = 0) out vec4 position; 5 | 6 | void main() { 7 | if(gl_VertexIndex == 0) position = vec4(-1, -1, 0, 1); 8 | if(gl_VertexIndex == 1) position = vec4(-1, 3, 0, 1); 9 | if(gl_VertexIndex == 2) position = vec4( 3, -1, 0, 1); 10 | gl_Position = position; 11 | } 12 | -------------------------------------------------------------------------------- /src/shaders/stars.frag: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | #include "pbr.glsl" 4 | 5 | layout(location = 0) in vec2 texcoord; 6 | layout(location = 1) in float magnitude; 7 | layout(location = 2) in vec4 position; 8 | 9 | layout(location = 0) out vec4 OutColor; 10 | 11 | layout(set = 0, binding = 0) uniform UniformBlock { 12 | Globals globals; 13 | }; 14 | layout(set = 0, binding = 2) uniform sampler linear; 15 | layout(set = 0, binding = 3) uniform sampler nearest; 16 | layout(set = 0, binding = 4) uniform texture2D sky; 17 | layout(set = 0, binding = 5) uniform texture2D transmittance; 18 | layout(set = 0, binding = 6) uniform texture2D skyview; 19 | 20 | #include "atmosphere.glsl" 21 | 22 | const float PI = 3.1415926535; 23 | 24 | void main() { 25 | vec2 v = texcoord * 2 - 1; 26 | float x = dot(v, v); 27 | 28 | float alpha = smoothstep(1, 0, x) * clamp(0, 1, exp(1-0.7*magnitude)); 29 | 30 | // // Sky calculations 31 | // vec4 r0 = globals.view_proj_inverse * vec4(position.xy, 1, 1); 32 | // vec4 r1 = globals.view_proj_inverse * vec4(position.xy, 1e-9, 1); 33 | // vec3 r = normalize(r1.xyz / r1.w - r0.xyz / r0.w); 34 | // vec3 camera = normalize(globals.camera); 35 | // vec3 sun = normalize(globals.sun_direction); 36 | // vec3 a = normalize(cross(camera, sun)); 37 | // vec3 b = normalize(cross(camera, a)); 38 | // float theta = asin(dot(r, camera)); 39 | // float phi = atan(dot(r, b), dot(r, a)) / PI * 0.5 + 0.5; 40 | // float camera_distance = length(globals.camera); 41 | // float min_theta = -PI/2 + asin(planetRadius / camera_distance); 42 | // float max_theta = camera_distance < atmosphereRadius ? PI/2 : -PI/2 + asin(atmosphereRadius / camera_distance); 43 | // float u = (theta - min_theta) / (max_theta - min_theta); 44 | // u = sqrt(u); 45 | // vec4 sv = texture(sampler2D(skyview, linear), (vec2(u, phi) * 127 + 0.5) / 128); 46 | 47 | // Non-physical transformation to make sure stars aren't visible from the ground. 48 | // alpha *= pow(sv.a * 16, 100); 49 | 50 | OutColor = vec4(vec3(1), alpha); 51 | // if (alpha <= 0) 52 | // OutColor = vec4(vec3(1,0,0), 1); 53 | // else 54 | // OutColor = vec4(1); 55 | } 56 | -------------------------------------------------------------------------------- /src/shaders/stars.vert: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | 4 | layout(location = 0) out vec2 texcoord; 5 | layout(location = 1) out float magnitude; 6 | layout(location = 2) out vec4 position; 7 | 8 | layout(set = 0, binding = 0, std140) uniform UniformBlock { 9 | Globals globals; 10 | }; 11 | 12 | struct Star { 13 | float declination; 14 | float ascension; 15 | float magnitude; 16 | float padding; 17 | }; 18 | layout(binding = 1) readonly buffer Stars { 19 | Star starfield[]; 20 | }; 21 | 22 | void main() { 23 | Star star = starfield[gl_VertexIndex / 6]; 24 | 25 | if(gl_VertexIndex % 6 == 0) texcoord = vec2(0, 0); 26 | if(gl_VertexIndex % 6 == 1) texcoord = vec2(1, 0); 27 | if(gl_VertexIndex % 6 == 2) texcoord = vec2(0, 1); 28 | if(gl_VertexIndex % 6 == 3) texcoord = vec2(1, 1); 29 | if(gl_VertexIndex % 6 == 4) texcoord = vec2(0, 1); 30 | if(gl_VertexIndex % 6 == 5) texcoord = vec2(1, 0); 31 | 32 | vec4 direction = vec4( 33 | cos(star.ascension - globals.sidereal_time) * cos(star.declination), 34 | sin(star.ascension - globals.sidereal_time) * cos(star.declination), 35 | sin(star.declination), 36 | 1e-15); 37 | 38 | magnitude = star.magnitude; 39 | 40 | gl_Position = globals.view_proj * direction; 41 | gl_Position.xy += (texcoord-0.5) * gl_Position.w * 4.0/vec2(globals.screen_width, globals.screen_height); 42 | position = gl_Position; 43 | } 44 | -------------------------------------------------------------------------------- /src/shaders/terrain.vert: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | 4 | layout(set = 0, binding = 0, std140) uniform UniformBlock { 5 | Globals globals; 6 | }; 7 | layout(set = 0, binding = 1, std140) readonly buffer Nodes { 8 | Node nodes[]; 9 | }; 10 | layout(set = 0, binding = 8) uniform texture2DArray displacements; 11 | 12 | layout(location = 0) out vec3 out_position; 13 | layout(location = 1) out vec2 out_texcoord; 14 | layout(location = 2) out float out_morph; 15 | layout(location = 3) out vec3 out_normal; 16 | layout(location = 4) out vec3 out_tangent; 17 | layout(location = 5) out vec3 out_bitangent; 18 | layout(location = 6) out vec2 out_i_position; 19 | layout(location = 7) flat out uint out_instance; 20 | 21 | const vec3 tangents[6] = vec3[6]( 22 | vec3(0,1,0), 23 | vec3(0,-1,0), 24 | vec3(1,0,0), 25 | vec3(-1,0,0), 26 | vec3(1,0,0), 27 | vec3(-1,0,0) 28 | ); 29 | 30 | vec3 sample_displacements(vec3 texcoord) { 31 | vec2 t = texcoord.xy * textureSize(displacements, 0).xy - 0.5; 32 | vec2 f = fract(t); 33 | vec4 w = vec4(f.x * (1-f.y), (1-f.x)*(1-f.y), (1-f.x)*f.y, f.x * f.y); 34 | return texelFetch(displacements, ivec3(t, texcoord.z), 0).xyz* (1-f.x) * (1-f.y) 35 | + texelFetch(displacements, ivec3(t+ivec2(1,0), texcoord.z), 0).xyz * (f.x) * (1-f.y) 36 | + texelFetch(displacements, ivec3(t+ivec2(1,1), texcoord.z), 0).xyz * (f.x) * (f.y) 37 | + texelFetch(displacements, ivec3(t+ivec2(0,1), texcoord.z), 0).xyz * (1-f.x) * (f.y); 38 | } 39 | 40 | void main() { 41 | uint resolution = 64;//nodes[gl_InstanceIndex].resolution; 42 | uvec2 base_origin = uvec2(0);//nodes[gl_InstanceIndex].base_origin; 43 | Node node = nodes[gl_InstanceIndex/4]; 44 | 45 | ivec2 iPosition = ivec2((gl_VertexIndex) % (resolution+1), 46 | (gl_VertexIndex) / (resolution+1)) + ivec2(base_origin); 47 | int displacements_slot = node.layers[DISPLACEMENTS_LAYER].slot; 48 | vec3 texcoord = layer_texcoord(node.layers[DISPLACEMENTS_LAYER], vec2(iPosition)/64.0); 49 | vec3 position = sample_displacements(texcoord) - nodes[displacements_slot].relative_position; 50 | 51 | float morph = 1 - smoothstep(0.9, 1, length(position) / node.min_distance); 52 | vec2 nPosition = mix(vec2((iPosition / 2) * 2), vec2(iPosition), morph); 53 | 54 | if (morph < 1.0) { 55 | int parent_displacements_slot = node.layers[PARENT_DISPLACEMENTS_LAYER].slot; 56 | if (parent_displacements_slot >= 0) { 57 | vec3 ptexcoord = layer_texcoord(node.layers[PARENT_DISPLACEMENTS_LAYER], vec2((iPosition/2)*2)/64.0); 58 | vec3 displacement = sample_displacements(ptexcoord) - nodes[parent_displacements_slot].relative_position; 59 | position = mix(displacement, position, morph); 60 | } else { 61 | vec3 itexcoord = layer_texcoord(node.layers[DISPLACEMENTS_LAYER], vec2((iPosition/2)*2)/64.0); 62 | vec3 displacement = sample_displacements(itexcoord) - nodes[displacements_slot].relative_position; 63 | position = mix(displacement, position, morph); 64 | } 65 | } 66 | 67 | vec3 normal = normalize(position + globals.camera); 68 | vec3 bitangent = normalize(cross(normal, tangents[node.face])); 69 | vec3 tangent = normalize(cross(normal, bitangent)); 70 | 71 | out_position = position; 72 | out_texcoord = nPosition / 64.0; 73 | out_morph = morph; 74 | out_normal = normal; 75 | out_tangent = tangent; 76 | out_bitangent = bitangent; 77 | out_i_position = vec2(iPosition); 78 | out_instance = gl_InstanceIndex/4; 79 | 80 | gl_Position = globals.view_proj * vec4(position, 1.0); 81 | } 82 | -------------------------------------------------------------------------------- /src/shaders/tree-billboards.frag: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | #include "pbr.glsl" 4 | 5 | layout(early_fragment_tests) in; 6 | 7 | layout(set = 0, binding = 0) uniform UniformBlock { 8 | Globals globals; 9 | }; 10 | layout(set = 0, binding = 8, std140) readonly buffer Nodes { 11 | Node nodes[]; 12 | }; 13 | 14 | layout(set = 0, binding = 1) uniform texture2DArray aerial_perspective; 15 | layout(set = 0, binding = 3) uniform sampler linear; 16 | 17 | layout(binding = 4) uniform texture2DArray billboards_albedo; 18 | layout(binding = 5) uniform texture2DArray billboards_normals; 19 | layout(binding = 6) uniform texture2DArray billboards_ao; 20 | layout(binding = 7) uniform texture2DArray billboards_depth; 21 | 22 | #ifndef SHADOWPASS 23 | layout(binding = 9) uniform texture2D shadowmap; 24 | layout(binding = 10) uniform samplerShadow shadow_sampler; 25 | layout(location = 0) out vec4 out_color; 26 | #endif 27 | 28 | layout(location = 0) in vec3 position; 29 | layout(location = 1) in vec3 color; 30 | layout(location = 2) in vec2 texcoord; 31 | layout(location = 3) in vec3 normal; 32 | layout(location = 4) flat sample in uint slot; 33 | layout(location = 5) in vec3 right; 34 | layout(location = 6) in vec3 up; 35 | 36 | vec3 extract_normal(vec2 n) { 37 | n = n * 2.0 - vec2(1.0); 38 | float y = sqrt(max(1.0 - dot(n, n),0)); 39 | return normalize(vec3(n.x, y, n.y)); 40 | } 41 | 42 | float mip_map_level(in vec2 texture_coordinate) 43 | { 44 | vec2 dx_vtc = dFdx(texture_coordinate); 45 | vec2 dy_vtc = dFdy(texture_coordinate); 46 | float delta_max_sqr = max(dot(dx_vtc, dx_vtc), dot(dy_vtc, dy_vtc)); 47 | return 0.5 * log2(delta_max_sqr); 48 | } 49 | 50 | void main() { 51 | vec4 albedo = texture(sampler2DArray(billboards_albedo, linear), vec3(texcoord/6.0, 0)); 52 | vec2 tx_normal = texture(sampler2DArray(billboards_normals, linear), vec3(texcoord/6.0, 0)).xy; 53 | float ao = texture(sampler2DArray(billboards_ao, linear), vec3(texcoord/6.0+1./6, 0), 0).x; 54 | float depth = texture(sampler2DArray(billboards_depth, linear), vec3(texcoord/6.0, 0)).x; 55 | 56 | albedo.rgb *= 0.15; 57 | albedo.rgb += (color-0.5) * 0.01; 58 | 59 | float tx_normal_z = sqrt(max(0, 1-dot(tx_normal, tx_normal))); 60 | vec3 true_normal = normalize(tx_normal.x * right - tx_normal.y * up + tx_normal_z * normal); 61 | //normalize(position+globals.camera);// 62 | // true_normal.y += 1; 63 | // true_normal = normalize(true_normal); 64 | true_normal = up; 65 | 66 | // albedo.rgb = 0*vec3(0.013,0.037,0.0); 67 | 68 | 69 | if (albedo.a < 0.5) 70 | discard; 71 | 72 | #ifndef SHADOWPASS 73 | 74 | float shadow = 0; 75 | vec4 proj_position = globals.shadow_view_proj * vec4(position + normal * depth*10, 1); 76 | //proj_position.xyz /= proj_position.w; 77 | vec2 shadow_coord = proj_position.xy * 0.5 * vec2(1,-1) + 0.5; 78 | if (all(greaterThan(shadow_coord,vec2(0))) && all(lessThan(shadow_coord,vec2(1)))) { 79 | float depth = proj_position.z - 4.0 / 102400.0; 80 | shadow = textureLod(sampler2DShadow(shadowmap, shadow_sampler), vec3(shadow_coord, depth), 0); 81 | } 82 | 83 | out_color = vec4(1); 84 | out_color.rgb = pbr(albedo.rgb, 85 | 0.4, 86 | position, 87 | true_normal, 88 | globals.camera, 89 | globals.sun_direction, 90 | vec3(100000.0)) * (1-shadow); 91 | 92 | 93 | out_color.rgb += (1 - ao) * albedo.rgb * 15000 * max(0, dot(up, globals.sun_direction));// * max(dot(true_normal, up), 0); 94 | 95 | // vec4 ap = texture(sampler2DArray(aerial_perspective, linear), layer_to_texcoord(AERIAL_PERSPECTIVE_LAYER)); 96 | // out_color.rgb *= ap.a * 16.0; 97 | // out_color.rgb += ap.rgb * 16.0; 98 | 99 | 100 | out_color = tonemap(out_color, globals.exposure, 2.2); 101 | 102 | // out_color.rgb = vec3(dot(globals.sun_direction,true_normal)); 103 | 104 | 105 | //out_color.rgb = vec3((tx_normal.x)*0.5 + 0.5); 106 | 107 | // out_color.rgb = vec3(1)*0.5 + 0.5; 108 | 109 | // if (dot(normal, up) < 0.001) 110 | // out_color.rgb = vec3(1,0,0); 111 | 112 | 113 | // float level = mip_map_level(texcoord*1024.0); 114 | // if (level < -1) out_color.rgb = vec3(.4,0,0); 115 | // else if (level < 0) out_color.rgb = vec3(1,0,0); 116 | // else if (level < 1) out_color.rgb = vec3(0,1,0); 117 | // else if (level < 5) out_color.rgb = vec3(0,0,1); 118 | #endif 119 | } 120 | -------------------------------------------------------------------------------- /src/shaders/tree-billboards.vert: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #include "declarations.glsl" 3 | 4 | layout(set = 0, binding = 0, std140) uniform UniformBlock { 5 | Globals globals; 6 | }; 7 | 8 | layout(set = 0, binding = 8, std140) readonly buffer Nodes { 9 | Node nodes[]; 10 | }; 11 | 12 | struct Entry { 13 | vec3 position; 14 | float angle; 15 | vec3 albedo; 16 | float height; 17 | vec4 padding0; 18 | vec4 padding1; 19 | }; 20 | layout(std430, binding = 2) readonly buffer DataBlock { 21 | Entry entries[]; 22 | } tree_billboards_storage; 23 | 24 | layout(set = 0, binding = 3) uniform sampler linear; 25 | // layout(set = 0, binding = 9) uniform texture2DArray displacements; 26 | 27 | layout(location = 0) out vec3 position; 28 | layout(location = 1) out vec3 color; 29 | layout(location = 2) out vec2 texcoord; 30 | layout(location = 3) out vec3 normal; 31 | layout(location = 4) flat sample out uint slot; 32 | layout(location = 5) out vec3 right; 33 | layout(location = 6) out vec3 up; 34 | 35 | const vec3 tangents[6] = vec3[6]( 36 | vec3(0,1,0), 37 | vec3(0,-1,0), 38 | vec3(1,0,0), 39 | vec3(-1,0,0), 40 | vec3(1,0,0), 41 | vec3(-1,0,0) 42 | ); 43 | 44 | void main() { 45 | uint entry_index = gl_VertexIndex / 4; 46 | uint index = gl_VertexIndex % 4; 47 | slot = gl_InstanceIndex / 16; 48 | 49 | Node node = nodes[slot]; 50 | Entry entry = tree_billboards_storage.entries[((slot - TREE_BILLBOARDS_BASE_SLOT) * 16 + gl_InstanceIndex % 16) * 1024 + entry_index]; 51 | position = entry.position - node.relative_position; 52 | 53 | up = normalize(position + globals.camera); 54 | vec3 bitangent = normalize(cross(up, tangents[node.face])); 55 | vec3 tangent = normalize(cross(up, bitangent)); 56 | 57 | float morph = 1 - smoothstep(0.7, .99, length(position) / node.min_distance); 58 | 59 | vec2 uv = vec2(0); 60 | if (index == 0) uv = vec2(0, 0); 61 | else if (index == 1) uv = vec2(1, 0); 62 | else if (index == 2) uv = vec2(0, 1); 63 | else if (index == 3) uv = vec2(1, 1); 64 | 65 | // if (index == 0) uv = vec2(-4, -.10); 66 | // else if (index == 1) uv = vec2(4, -.10); 67 | // else if (index == 2) uv = vec2(-1, 1); 68 | // else if (index == 3) uv = vec2(1, 1); 69 | 70 | right = normalize(cross(position, up)); 71 | 72 | if (morph > 0) 73 | position += 30*(up * (1-uv.y) + right * (uv.x-0.5)); 74 | 75 | color = entry.albedo;//vec3(0.33,0.57,0.0)*.13; 76 | texcoord = uv; 77 | normal = normalize(cross(right, up)); 78 | 79 | gl_Position = globals.view_proj * vec4(position, 1.0); 80 | } -------------------------------------------------------------------------------- /src/speedtree_xml.rs: -------------------------------------------------------------------------------- 1 | #![warn(non_snake_case)] 2 | 3 | use std::collections::HashMap; 4 | use std::str::FromStr; 5 | 6 | use serde::de::Error; 7 | use serde::{Deserialize, Deserializer}; 8 | use std::ops::Deref; 9 | use std::ops::Range; 10 | 11 | #[derive(Deserialize, Debug)] 12 | #[serde(rename_all = "PascalCase")] 13 | #[allow(unused)] 14 | struct SpeedTreeRaw { 15 | #[serde(rename = "@VersionMajor")] 16 | version_major: u8, 17 | #[serde(rename = "@VersionMinor")] 18 | version_minor: u8, 19 | #[serde(rename = "@UserData")] 20 | user_data: String, 21 | #[serde(rename = "@Source")] 22 | source: String, 23 | wind: Wind, 24 | objects: Objects, 25 | } 26 | 27 | #[derive(Deserialize, Debug)] 28 | #[serde(rename_all = "PascalCase")] 29 | #[allow(unused)] 30 | struct Wind { 31 | #[serde(rename = "@Bend")] 32 | bend: f32, 33 | #[serde(rename = "@BranchAmplitude")] 34 | branch_amplitude: f32, 35 | #[serde(rename = "@LeafAmplitude")] 36 | leaf_amplitude: f32, 37 | #[serde(rename = "@DetailFrequency")] 38 | detail_frequency: f32, 39 | #[serde(rename = "@GlobalHeight")] 40 | global_height: f32, 41 | } 42 | 43 | #[derive(Deserialize, Debug)] 44 | #[serde(rename_all = "PascalCase")] 45 | #[allow(unused)] 46 | struct Objects { 47 | #[serde(rename = "@Count")] 48 | count: u32, 49 | #[serde(rename = "@LodNear")] 50 | lod_near: f32, 51 | #[serde(rename = "@LodFar")] 52 | lod_far: f32, 53 | #[serde(rename = "@BoundsMinX")] 54 | bounds_min_x: f32, 55 | #[serde(rename = "@BoundsMinY")] 56 | bounds_min_y: f32, 57 | #[serde(rename = "@BoundsMinZ")] 58 | bounds_min_z: f32, 59 | #[serde(rename = "@BoundsMaxX")] 60 | bounds_max_x: f32, 61 | #[serde(rename = "@BoundsMaxY")] 62 | bounds_max_y: f32, 63 | #[serde(rename = "@BoundsMaxZ")] 64 | bounds_max_z: f32, 65 | #[serde(rename = "Object", default)] 66 | objects: Vec, 67 | } 68 | 69 | #[derive(Deserialize, Debug)] 70 | #[serde(rename_all = "PascalCase")] 71 | struct Object { 72 | #[serde(rename = "@ID")] 73 | id: u32, 74 | #[serde(rename = "@Name")] 75 | name: String, 76 | #[serde(rename = "@ParentID")] 77 | parent_id: Option, 78 | points: Option, 79 | vertices: Option, 80 | triangles: Option, 81 | } 82 | 83 | #[derive(Deserialize, Debug)] 84 | #[serde(rename_all = "PascalCase")] 85 | struct Points { 86 | x: List, 87 | y: List, 88 | z: List, 89 | lod_x: List, 90 | lod_y: List, 91 | lod_z: List, 92 | } 93 | 94 | #[derive(Deserialize, Debug)] 95 | #[serde(rename_all = "PascalCase")] 96 | #[allow(unused)] 97 | struct Vertices { 98 | #[serde(rename = "@Count")] 99 | count: u32, 100 | normal_x: List, 101 | normal_y: List, 102 | normal_z: List, 103 | binormal_x: List, 104 | binormal_y: List, 105 | binormal_z: List, 106 | tangent_x: List, 107 | tangent_y: List, 108 | tangent_z: List, 109 | texcoord_u: List, 110 | texcoord_v: List, 111 | #[serde(rename = "AO")] 112 | ao: List, 113 | vertex_color_r: List, 114 | vertex_color_g: List, 115 | vertex_color_b: List, 116 | } 117 | 118 | #[derive(Deserialize, Debug)] 119 | #[serde(rename_all = "PascalCase")] 120 | struct Triangles { 121 | #[serde(rename = "@Count")] 122 | count: u32, 123 | #[serde(rename = "@Material")] 124 | _material: u32, 125 | point_indices: List, 126 | vertex_indices: List, 127 | } 128 | 129 | #[derive(Debug)] 130 | struct List(Vec); 131 | impl<'de, T: Deserialize<'de> + FromStr> serde::Deserialize<'de> for List { 132 | fn deserialize(deserializer: D) -> Result 133 | where 134 | D: Deserializer<'de>, 135 | { 136 | let s = String::deserialize(deserializer)?; 137 | 138 | match s.split(" ").map(T::from_str).collect() { 139 | Err(_) => Err(D::Error::custom("Failed to parse space seperated list")), 140 | Ok(values) => Ok(Self(values)), 141 | } 142 | } 143 | } 144 | impl Deref for List { 145 | type Target = Vec; 146 | fn deref(&self) -> &Vec { 147 | &self.0 148 | } 149 | } 150 | 151 | #[repr(C)] 152 | #[derive(Copy, Clone, Debug)] 153 | pub(crate) struct Vertex { 154 | position: [f32; 3], 155 | ao: f32, 156 | 157 | lod_position: [f32; 3], 158 | color: u32, 159 | normal: [f32; 3], 160 | texcoord_u: f32, 161 | binormal: [f32; 3], 162 | texcoord_v: f32, 163 | } 164 | unsafe impl bytemuck::Pod for Vertex {} 165 | unsafe impl bytemuck::Zeroable for Vertex {} 166 | 167 | pub(crate) struct SpeedTreeModel { 168 | pub vertices: Vec, 169 | pub indices: Vec, 170 | pub lods: Vec>, 171 | } 172 | 173 | pub(crate) fn parse_xml(contents: &str) -> Result { 174 | let t: SpeedTreeRaw = quick_xml::de::from_str(contents)?; 175 | 176 | let mut object_names = HashMap::new(); 177 | let mut merged_objects = HashMap::new(); 178 | for object in &t.objects.objects { 179 | if let Some(parent_id) = object.parent_id { 180 | let points = object.points.as_ref().unwrap(); 181 | let vertices = object.vertices.as_ref().unwrap(); 182 | let triangles = object.triangles.as_ref().unwrap(); 183 | 184 | let (output_vertices, output_indices) = 185 | merged_objects.entry(parent_id).or_insert((Vec::new(), Vec::new())); 186 | 187 | assert_eq!(triangles.count * 3, triangles.point_indices.len() as u32); 188 | 189 | let mut ids_to_index = HashMap::new(); 190 | for tri_index in 0..(triangles.count * 3) as usize { 191 | let pid = triangles.point_indices[tri_index] as usize; 192 | let vid = triangles.vertex_indices[tri_index] as usize; 193 | 194 | if let Some(index) = ids_to_index.get(&(pid, vid)) { 195 | output_indices.push(*index); 196 | continue; 197 | } 198 | let index = output_vertices.len() as u32; 199 | ids_to_index.insert((pid, vid), index); 200 | output_indices.push(index); 201 | output_vertices.push(Vertex { 202 | position: [points.x[pid], points.z[pid], points.y[pid]], 203 | lod_position: [points.lod_x[pid], points.lod_z[pid], points.lod_y[pid]], 204 | normal: [ 205 | vertices.normal_x[vid], 206 | vertices.normal_z[vid], 207 | vertices.normal_y[vid], 208 | ], 209 | binormal: [ 210 | vertices.binormal_x[vid], 211 | vertices.binormal_z[vid], 212 | vertices.binormal_y[vid], 213 | ], 214 | texcoord_u: vertices.texcoord_u[vid], 215 | texcoord_v: vertices.texcoord_v[vid], 216 | ao: vertices.ao[vid], 217 | color: ((vertices.vertex_color_r[vid].min(1.0).max(0.0) * 255.0) as u32) 218 | | ((vertices.vertex_color_g[vid].min(1.0).max(0.0) * 255.0) as u32) << 8 219 | | ((vertices.vertex_color_b[vid].min(1.0).max(0.0) * 255.0) as u32) << 16, 220 | }) 221 | } 222 | } else { 223 | object_names.insert(object.name.clone(), object.id); 224 | } 225 | } 226 | 227 | let mut lods = Vec::new(); 228 | let mut vertices = Vec::new(); 229 | let mut indices = Vec::new(); 230 | for name in ["LOD0", "LOD1", "LOD2", "LOD3"] { 231 | if let Some(id) = object_names.get(name) { 232 | let mut merged = merged_objects.remove(id).unwrap(); 233 | 234 | let base_vertex = vertices.len() as u32; 235 | let start = indices.len() as u32; 236 | let end = (indices.len() + merged.1.len()) as u32; 237 | 238 | for index in merged.1 { 239 | indices.push(index + base_vertex); 240 | } 241 | vertices.append(&mut merged.0); 242 | lods.push(start..end); 243 | } 244 | } 245 | 246 | Ok(SpeedTreeModel { vertices, indices, lods }) 247 | } 248 | 249 | #[cfg(test)] 250 | mod tests { 251 | #[test] 252 | fn vertex_size() { 253 | assert_eq!(std::mem::size_of::(), 64); 254 | } 255 | 256 | // #[test] 257 | // fn parse_xml() { 258 | // let t: super::SpeedTreeRaw = 259 | // quick_xml::de::from_str(include_str!("../assets/Tree/Oak_English_Sapling.xml")) 260 | // .unwrap(); 261 | // } 262 | } 263 | -------------------------------------------------------------------------------- /src/stream.rs: -------------------------------------------------------------------------------- 1 | use crate::cache::layer::LayerType; 2 | use crate::mapfile::MapFile; 3 | use anyhow::Error; 4 | use futures::{FutureExt, StreamExt}; 5 | use std::io::{Cursor, Read}; 6 | use std::sync::Arc; 7 | use std::thread; 8 | use std::time::Instant; 9 | use terra_types::VNode; 10 | use tokio::runtime::Runtime; 11 | use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; 12 | use vec_map::VecMap; 13 | use zip::result::ZipError; 14 | 15 | #[derive(Debug)] 16 | pub(crate) struct TileResult { 17 | pub node: VNode, 18 | pub layers: VecMap>, 19 | } 20 | 21 | pub(crate) struct TileStreamerEndpoint { 22 | sender: UnboundedSender<(VNode, Instant)>, 23 | receiver: crossbeam::channel::Receiver, 24 | join_handle: Option>, 25 | num_inflight: usize, 26 | } 27 | impl TileStreamerEndpoint { 28 | pub(crate) fn new( 29 | mapfile: Arc, 30 | transcode_format: wgpu::TextureFormat, 31 | ) -> Result { 32 | let (sender, requests) = unbounded_channel(); 33 | let (results, receiver) = crossbeam::channel::unbounded(); 34 | 35 | let rt = Runtime::new()?; 36 | let join_handle = Some(thread::spawn(move || { 37 | rt.block_on( 38 | TileStreamer { 39 | requests, 40 | results, 41 | // heightmap_tiles: HeightmapCache::new( 42 | // mapfile.layers()[LayerType::Heightmaps].texture_resolution as usize, 43 | // mapfile.layers()[LayerType::Heightmaps].texture_border_size as usize, 44 | // 128, 45 | // ), 46 | transcode_format, 47 | mapfile, 48 | } 49 | .run(), 50 | ) 51 | .unwrap(); 52 | })); 53 | 54 | Ok(Self { sender, receiver, join_handle, num_inflight: 0 }) 55 | } 56 | 57 | pub(crate) fn request_tile(&mut self, node: VNode) { 58 | if let Err(_) = self.sender.send((node, Instant::now())) { 59 | // The worker thread has panicked (we still have the sender open, so that cannot be why 60 | // it exited). 61 | self.join_handle.take().unwrap().join().unwrap(); 62 | unreachable!("TileStreamer exited without panicking"); 63 | } 64 | self.num_inflight += 1; 65 | } 66 | 67 | pub(crate) fn try_complete(&mut self) -> Option { 68 | if let Ok(result) = self.receiver.try_recv() { 69 | self.num_inflight -= 1; 70 | Some(result) 71 | } else { 72 | None 73 | } 74 | } 75 | 76 | pub(crate) fn num_inflight(&self) -> usize { 77 | self.num_inflight 78 | } 79 | } 80 | 81 | struct TileStreamer { 82 | requests: UnboundedReceiver<(VNode, Instant)>, 83 | results: crossbeam::channel::Sender, 84 | transcode_format: wgpu::TextureFormat, 85 | mapfile: Arc, 86 | } 87 | 88 | impl TileStreamer { 89 | fn parse_tile( 90 | node: VNode, 91 | bytes: &[u8], 92 | _transcode_format: wgpu::TextureFormat, 93 | ) -> Result { 94 | let mut zip = zip::ZipArchive::new(Cursor::new(bytes))?; 95 | let mut result = TileResult { node, layers: VecMap::new() }; 96 | 97 | let mut get_file = |name| -> Result>, Error> { 98 | match zip.by_name(name) { 99 | Ok(mut file) => { 100 | let mut bytes = Vec::new(); 101 | file.read_to_end(&mut bytes)?; 102 | Ok(Some(bytes)) 103 | } 104 | Err(ZipError::FileNotFound) => Ok(None), 105 | Err(e) => Err(e.into()), 106 | } 107 | }; 108 | 109 | let decode_nonempty = |bytes: Vec| -> Result>, Error> { 110 | if bytes.is_empty() { 111 | Ok(None) 112 | } else { 113 | Ok(Some(zstd::decode_all(Cursor::new( 114 | &ktx2::Reader::new(bytes)?.levels().next().expect("ktx2 has no levels"), 115 | ))?)) 116 | } 117 | }; 118 | 119 | result.layers.insert( 120 | LayerType::BaseHeightmaps.index(), 121 | decode_nonempty(get_file("heights.ktx2")?.expect("layer missing"))? 122 | .unwrap_or_else(|| vec![0u8; 521 * 521 * 2]), 123 | ); 124 | result.layers.insert( 125 | LayerType::TreeCover.index(), 126 | decode_nonempty(get_file("treecover.ktx2")?.expect("layer missing"))? 127 | .unwrap_or_else(|| vec![0u8; 516 * 516]), 128 | ); 129 | result.layers.insert( 130 | LayerType::LandFraction.index(), 131 | decode_nonempty(get_file("landfraction.ktx2")?.expect("layer missing"))? 132 | .unwrap_or_else(|| vec![0u8; 516 * 516]), 133 | ); 134 | 135 | if let Some(bytes) = get_file("waterlevel.ktx2")? { 136 | result.layers.insert( 137 | LayerType::WaterLevel.index(), 138 | decode_nonempty(bytes)?.unwrap_or_else(|| vec![0u8; 521 * 521 * 2]), 139 | ); 140 | } 141 | if let Some(bytes) = get_file("albedo.ktx2")? { 142 | result.layers.insert( 143 | LayerType::BaseAlbedo.index(), 144 | decode_nonempty(bytes)?.unwrap_or_else(|| vec![0u8; 516 * 516 * 4]), 145 | ); 146 | } 147 | 148 | if node.level() == 0 { 149 | assert!(result.layers.contains_key(LayerType::BaseHeightmaps.index())); 150 | assert!(result.layers.contains_key(LayerType::TreeCover.index())); 151 | assert!(result.layers.contains_key(LayerType::LandFraction.index())); 152 | assert!(result.layers.contains_key(LayerType::BaseAlbedo.index())); 153 | } 154 | 155 | Ok(result) 156 | } 157 | 158 | async fn run(self) -> Result<(), Error> { 159 | let TileStreamer { mut requests, results, mapfile, transcode_format } = self; 160 | let mapfile = &*mapfile; 161 | 162 | let mut pending = futures::stream::futures_unordered::FuturesUnordered::new(); 163 | loop { 164 | futures::select! { 165 | tile_result = pending.select_next_some() => { 166 | results.send(tile_result?)?; 167 | }, 168 | node = requests.recv().fuse() => if let Some((node, _start)) = node { 169 | pending.push(async move { 170 | match mapfile.read_tile(node).await? { 171 | Some(raw_data) => { 172 | tokio::task::spawn_blocking(move || Self::parse_tile(node, &raw_data, transcode_format)).await.unwrap() 173 | } 174 | None => { 175 | let mut result = TileResult { 176 | node, 177 | layers: VecMap::new(), 178 | }; 179 | result.layers.insert(LayerType::BaseHeightmaps.index(), bytemuck::cast_slice(&vec![0u16; 521 * 521]).to_vec()); 180 | result.layers.insert(LayerType::TreeCover.index(), vec![0u8; 516 * 516]); 181 | result.layers.insert(LayerType::LandFraction.index(), vec![0u8; 516 * 516]); 182 | Ok(result) 183 | } 184 | } 185 | }.boxed()); 186 | }, 187 | complete => break, 188 | } 189 | } 190 | Ok(()) 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "terra-types" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0.70" 10 | cgmath = { version = "0.18.0", features = ["serde"], git = "https://github.com/rustgd/cgmath", rev = "d5e765db61cf9039cb625a789a59ddf6b6ab2337" } 11 | lazy_static = "1.4.0" 12 | serde = { version = "1.0.158", features = ["derive"] } 13 | -------------------------------------------------------------------------------- /types/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate lazy_static; 3 | 4 | use serde::{Deserialize, Serialize}; 5 | use std::f64::consts::PI; 6 | 7 | mod math; 8 | mod node; 9 | 10 | pub use math::{BoundingBox, InfiniteFrustum}; 11 | pub use node::{VNode, NODE_OFFSETS}; 12 | 13 | pub const EARTH_RADIUS: f64 = 6371000.0; 14 | pub const EARTH_CIRCUMFERENCE: f64 = 2.0 * PI * EARTH_RADIUS; 15 | pub const EARTH_SEMIMAJOR_AXIS: f64 = 6378137.0; 16 | pub const EARTH_SEMIMINOR_AXIS: f64 = 6356752.314245; 17 | pub const ROOT_SIDE_LENGTH: f32 = (EARTH_CIRCUMFERENCE * 0.25) as f32; 18 | pub const MAX_QUADTREE_LEVEL: u8 = VNode::LEVEL_CELL_5MM; 19 | 20 | #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Serialize, Deserialize)] 21 | pub struct Priority(f32); 22 | impl Priority { 23 | pub fn cutoff() -> Self { 24 | Priority(1.0) 25 | } 26 | pub fn none() -> Self { 27 | Priority(-1.0) 28 | } 29 | pub fn from_f32(value: f32) -> Self { 30 | assert!(value.is_finite()); 31 | Priority(value) 32 | } 33 | } 34 | impl Eq for Priority {} 35 | impl Ord for Priority { 36 | fn cmp(&self, other: &Self) -> ::std::cmp::Ordering { 37 | self.partial_cmp(other).unwrap() 38 | } 39 | } 40 | 41 | pub struct VFace(pub u8); 42 | impl std::fmt::Display for VFace { 43 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 44 | write!( 45 | f, 46 | "{}", 47 | match self.0 { 48 | 0 => "0E", 49 | 1 => "180E", 50 | 2 => "90E", 51 | 3 => "90W", 52 | 4 => "N", 53 | 5 => "S", 54 | _ => unreachable!(), 55 | } 56 | ) 57 | } 58 | } 59 | 60 | pub struct VSector(pub VFace, pub u8, pub u8); 61 | impl std::fmt::Display for VSector { 62 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 63 | write!(f, "S-{}-x{:03}-y{:03}", self.0, self.1, self.2) 64 | } 65 | } 66 | 67 | #[cfg(test)] 68 | mod tests { 69 | #[test] 70 | fn it_works() { 71 | let result = 2 + 2; 72 | assert_eq!(result, 4); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /types/src/math.rs: -------------------------------------------------------------------------------- 1 | use cgmath::*; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Clone, Copy, Serialize, Deserialize)] 5 | pub struct BoundingBox { 6 | pub min: Point3, 7 | pub max: Point3, 8 | } 9 | 10 | impl BoundingBox { 11 | #[allow(unused)] 12 | pub fn new(min: Point3, max: Point3) -> Self { 13 | Self { min, max } 14 | } 15 | #[allow(unused)] 16 | pub fn distance(&self, p: Point3) -> f32 { 17 | self.square_distance(p).sqrt() 18 | } 19 | #[allow(unused)] 20 | pub fn square_distance(&self, p: Point3) -> f32 { 21 | let dx = (self.min.x - p.x).max(0.0).max(p.x - self.max.x); 22 | let dy = (self.min.y - p.y).max(0.0).max(p.y - self.max.y); 23 | let dz = (self.min.z - p.z).max(0.0).max(p.z - self.max.z); 24 | dx * dx + dy * dy + dz * dz 25 | } 26 | 27 | #[allow(unused)] 28 | pub fn square_distance_xz(&self, p: Point3) -> f32 { 29 | let dx = (self.min.x - p.x).max(0.0).max(p.x - self.max.x); 30 | let dz = (self.min.z - p.z).max(0.0).max(p.z - self.max.z); 31 | dx * dx + dz * dz 32 | } 33 | } 34 | 35 | #[derive(Clone, Debug)] 36 | pub struct InfiniteFrustum { 37 | pub planes: [Vector4; 5], 38 | } 39 | impl InfiniteFrustum { 40 | fn normalize_plane(plane: Vector4) -> Vector4 { 41 | let magnitude = (plane.x * plane.x + plane.y * plane.y + plane.z * plane.z).sqrt(); 42 | plane / magnitude 43 | } 44 | 45 | pub fn from_matrix(m: Matrix4) -> Self { 46 | let m = m.transpose(); 47 | Self { 48 | planes: [ 49 | Self::normalize_plane(m.w + m.x), 50 | Self::normalize_plane(m.w - m.x), 51 | Self::normalize_plane(m.w + m.y), 52 | Self::normalize_plane(m.w - m.y), 53 | Self::normalize_plane(m.w + m.z), 54 | ], 55 | } 56 | } 57 | 58 | pub fn intersects_sphere(&self, center: Vector3, radius_squared: f64) -> bool { 59 | for p in &self.planes[0..5] { 60 | let distance = p.x * center.x + p.y * center.y + p.z * center.z + p.w; 61 | if distance < 0.0 && distance * distance > radius_squared { 62 | return false; 63 | } 64 | } 65 | true 66 | } 67 | } 68 | --------------------------------------------------------------------------------