├── .github └── workflows │ └── nodejs.yml ├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── generator │ └── start-scene.ts ├── index.ts ├── octree │ ├── chunk.ts │ ├── grid.ts │ ├── util.ts │ └── worker │ │ ├── block-generator.ts │ │ ├── mesh-generator.ts │ │ └── node.ts ├── render │ ├── camera.ts │ ├── context.ts │ ├── loop.ts │ └── pipeline │ │ ├── shared-node │ │ ├── denoiser │ │ │ ├── denoiser.fs.glsl │ │ │ ├── denoiser.ts │ │ │ └── denoiser.vs.glsl │ │ ├── edit │ │ │ ├── edit-node.fs.glsl │ │ │ ├── edit-node.ts │ │ │ └── edit-node.vs.glsl │ │ └── output │ │ │ ├── output.fs.glsl │ │ │ ├── output.ts │ │ │ └── output.vs.glsl │ │ ├── v1 │ │ ├── node │ │ │ ├── chunk-node │ │ │ │ ├── chunk-node.fs.glsl │ │ │ │ ├── chunk-node.ts │ │ │ │ └── chunk-node.vs.glsl │ │ │ └── rt-light │ │ │ │ ├── rt-light.fs.glsl │ │ │ │ ├── rt-light.ts │ │ │ │ └── rt-light.vs.glsl │ │ └── pipeline-v1.ts │ │ └── v2 │ │ ├── node │ │ └── rt-chunk-node │ │ │ ├── rt-chunk-node.fs.glsl │ │ │ ├── rt-chunk-node.ts │ │ │ └── rt-chunk-node.vs.glsl │ │ └── pipeline-v2.ts ├── server.js └── tsconfig.json └── webpack.config.js /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [12.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: npm install, build, and test 21 | run: | 22 | npm install 23 | npm run build --if-present 24 | env: 25 | CI: true 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .idea 4 | /build/ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 André Furchner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Actions Status](https://github.com/FoxelFox/voxel-octree/workflows/build/badge.svg)](https://github.com/FoxelFox/voxel-octree/actions) 2 | 3 | 4 | # voxel-octree 5 | 6 | Controls: 7 | * Movement W A S D + Mouse 8 | * select color with 1-9 (0 is light block) 9 | * place Voxel witch right click 10 | 11 | Live Demo: https://64f.de/voxel-octree-rt-v7/ 12 | 13 | [![IMAGE ALT TEXT HERE](https://img.youtube.com/vi/1Sfj7V59kPY/0.jpg)](https://www.youtube.com/watch?v=1Sfj7V59kPY) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "voxel-octree", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack --mode production", 8 | "build-w": "webpack --mode development -w --devtool source-map", 9 | "server": "ws --stack src/server.js spa static index" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/FoxelFox/voxel-octree.git" 14 | }, 15 | "author": "", 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/FoxelFox/voxel-octree/issues" 19 | }, 20 | "homepage": "https://github.com/FoxelFox/voxel-octree#readme", 21 | "devDependencies": { 22 | "@babel/types": "^7.12.12", 23 | "@foxel_fox/glib": "^0.0.20", 24 | "@types/gl-matrix": "^3.2.0", 25 | "@types/node": "^14.14.21", 26 | "@types/webgl2": "^0.0.5", 27 | "gl-bench": "^1.0.9", 28 | "gl-matrix": "^3.3.0", 29 | "html-webpack-plugin": "^4.5.1", 30 | "local-web-server": "^4.2.1", 31 | "shader-loader": "^1.3.1", 32 | "threads": "^1.6.3", 33 | "threads-plugin": "^1.4.0", 34 | "ts-loader": "^8.0.14", 35 | "typescript": "^4.1.3", 36 | "webpack": "^4.46.0", 37 | "webpack-cli": "^4.3.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/generator/start-scene.ts: -------------------------------------------------------------------------------- 1 | import {OctreeGrid} from "../octree/grid"; 2 | 3 | export function generateStartScene(grid: OctreeGrid) { 4 | grid.modify([0, 0, 0], [1023, 1023, 255], 1); 5 | grid.modify([0, 0, 0], [255, 1023, 511], 1); 6 | 7 | grid.modify([256 + 64, 256+128, 256], [256 + 64 + 64 -1, 256+128 +64-1, 319], 1); 8 | grid.modify([256 + 64, 256-128, 256], [256 + 64 + 64 -1, 256-128 +64-1, 319], 1); 9 | 10 | grid.modify([512 + 128, 768+64, 256], [512 + 128 + 64 -1, 768+128 -1, 319], 1); 11 | grid.modify([512 + 128, 768+64 -256, 256], [512 + 128 + 64 -1, 768-128 -1, 319], 1); 12 | 13 | grid.modify([256, 256, 256], [447, 256+64-1, 447], 2); 14 | grid.modify([576, 768 - 64, 256], [767, 831 -64, 447], 3); 15 | grid.modify([768, 0, 0], [1023, 1023, 511], 1); 16 | grid.modify([0, 512, 256], [255, 767, 511], 0); 17 | grid.modify([768, 256, 256], [1023, 511, 511], 0); 18 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import {OctreeGrid} from "./octree/grid"; 2 | import "./render/context"; 3 | import {start, init} from "./render/loop"; 4 | import {spawn, Worker} from "threads/dist"; 5 | 6 | async function main() { 7 | 8 | const grid = await spawn(new Worker("./octree/grid")); 9 | await grid.initThreads(); 10 | await init(); 11 | 12 | start(grid); 13 | } 14 | 15 | main().then(() => { 16 | 17 | }); -------------------------------------------------------------------------------- /src/octree/chunk.ts: -------------------------------------------------------------------------------- 1 | import {OctreeNode} from "./worker/node"; 2 | 3 | export interface Chunk { 4 | id: number[] 5 | tree: OctreeNode 6 | } 7 | 8 | export interface VoxelsOnGPU { 9 | id: number[] 10 | v?: any 11 | rt?: any 12 | colors?: any 13 | index?: { v: number, rt: number } 14 | } -------------------------------------------------------------------------------- /src/octree/grid.ts: -------------------------------------------------------------------------------- 1 | import {spawn, Worker, Transfer, expose, Pool} from "threads/dist"; 2 | import {modify} from "./worker/node"; 3 | import {map3D1D} from "./util"; 4 | import {Chunk, VoxelsOnGPU} from "./chunk"; 5 | import {Observable} from "threads/dist/observable"; 6 | import {MeshGeneratorWorker} from "./worker/mesh-generator"; 7 | 8 | 9 | const queue: Chunk[] = []; 10 | const chunks: { [key: number]: Chunk } = {}; 11 | const meshes: { [key: number]: VoxelsOnGPU } = {}; 12 | const lockedBuffer: { [key: number]: boolean } = {}; 13 | const scale = 1024; 14 | let meshObserver; 15 | let pool; 16 | let results = []; 17 | 18 | function getChunkID(position: number[]): number[] { 19 | return [ 20 | Math.floor(position[0] / scale), 21 | Math.floor(position[1] / scale), 22 | Math.floor(position[2] / scale) 23 | ]; 24 | } 25 | 26 | function getChuckByID(chunkID: number[]): Chunk { 27 | return chunks[map3D1D(chunkID)]; 28 | } 29 | 30 | function updateMesh(chunk: Chunk) { 31 | if (queue.findIndex(c => 32 | c.id[0] === chunk.id[0] && 33 | c.id[1] === chunk.id[1] && 34 | c.id[2] === chunk.id[2] 35 | ) === -1) { 36 | queue.push(chunk); 37 | } 38 | } 39 | 40 | function balanceWork() { 41 | 42 | while (queue[0]) { 43 | const chunk = queue[0]; 44 | const chunkID = map3D1D(chunk.id); 45 | const chunkMesh = meshes[chunkID]; 46 | 47 | if (lockedBuffer[chunkID]) { 48 | break; 49 | } 50 | 51 | queue.shift(); 52 | 53 | lockedBuffer[chunkID] = true; 54 | pool.queue(async worker => { 55 | worker.work(chunk.id, JSON.stringify(chunks), chunkMesh.v ? chunkMesh.v : undefined, chunkMesh.v ? chunkMesh.rt : undefined, chunkMesh.v ? chunkMesh.colors : undefined).then((mesh) => { 56 | if (!chunkMesh.v) { 57 | chunkMesh.v = mesh.v; 58 | chunkMesh.rt = mesh.rt; 59 | chunkMesh.colors = mesh.colors; 60 | } 61 | chunkMesh.index = mesh.index; 62 | results.push({ 63 | v: mesh.v, 64 | rt: mesh.rt, 65 | colors: mesh.colors, 66 | id: chunk.id, 67 | index: chunkMesh.index 68 | }); 69 | }); 70 | }) 71 | } 72 | } 73 | 74 | 75 | const octreeGrid = { 76 | 77 | async initThreads() { 78 | const maxWorkerThreads = Math.max(1, navigator.hardwareConcurrency -2); 79 | pool = Pool(() => spawn(new Worker("./worker/mesh-generator")), maxWorkerThreads) 80 | }, 81 | 82 | meshChanges() { 83 | return new Observable(observer => { 84 | meshObserver = observer; 85 | }) 86 | }, 87 | 88 | meshUploaded(id: number) { 89 | lockedBuffer[id] = false; 90 | balanceWork() 91 | }, 92 | 93 | async modify(p1: number[], p2: number[], value: number) { 94 | 95 | const startChunkIDCoords = getChunkID(p1); 96 | const endChunkIDCoords = getChunkID(p2); 97 | 98 | for (let x = startChunkIDCoords[0]; x <= endChunkIDCoords[0]; x++) { 99 | for (let y = startChunkIDCoords[1]; y <= endChunkIDCoords[1]; y++) { 100 | for (let z = startChunkIDCoords[2]; z <= endChunkIDCoords[2]; z++) { 101 | 102 | const chunkAbsStartX = x * scale; 103 | const chunkAbsEndX = (x + 1) * scale -1; 104 | const chunkAbsStartY = y * scale; 105 | const chunkAbsEndY = (y + 1) * scale -1; 106 | const chunkAbsStartZ = z * scale; 107 | const chunkAbsEndZ = (z + 1) * scale -1; 108 | 109 | const relStartPoint = [ 110 | p1[0] > chunkAbsStartX ? p1[0] - chunkAbsStartX : 0, 111 | p1[1] > chunkAbsStartY ? p1[1] - chunkAbsStartY : 0, 112 | p1[2] > chunkAbsStartZ ? p1[2] - chunkAbsStartZ : 0 113 | ]; 114 | 115 | const relEndPoint = [ 116 | p2[0] <= chunkAbsEndX ? p2[0] % scale : scale - 1, 117 | p2[1] <= chunkAbsEndY ? p2[1] % scale : scale - 1, 118 | p2[2] <= chunkAbsEndZ ? p2[2] % scale : scale - 1 119 | ]; 120 | 121 | relEndPoint[0] = relEndPoint[0] < 0 ? 1024 + relEndPoint[0] : relEndPoint[0] 122 | relEndPoint[1] = relEndPoint[1] < 0 ? 1024 + relEndPoint[1] : relEndPoint[1] 123 | relEndPoint[2] = relEndPoint[2] < 0 ? 1024 + relEndPoint[2] : relEndPoint[2] 124 | 125 | const id = [x, y, z]; 126 | let chunk = chunks[map3D1D(id)]; 127 | 128 | if (!chunk) { 129 | let tree = { data: 0 }; 130 | chunk = chunks[map3D1D(id)] = { 131 | id, 132 | tree 133 | }; 134 | 135 | meshes[map3D1D(id)] = { 136 | id, 137 | v: null, 138 | rt: null 139 | } 140 | } 141 | 142 | const info = { 143 | size: scale, 144 | node: chunk.tree, 145 | position: [0, 0, 0], 146 | depth: 0 147 | }; 148 | 149 | modify(info, relStartPoint, relEndPoint, value); 150 | 151 | updateMesh(chunk); 152 | } 153 | } 154 | } 155 | balanceWork(); 156 | }, 157 | 158 | getNext() { 159 | return results.shift(); 160 | } 161 | 162 | }; 163 | 164 | export type OctreeGrid = typeof octreeGrid; 165 | expose(octreeGrid); -------------------------------------------------------------------------------- /src/octree/util.ts: -------------------------------------------------------------------------------- 1 | export function map3D1D(p: number[]): number { 2 | return p[0] + 16384 * (p[1] + 16384 * p[2]); 3 | } 4 | 5 | export const childIndices = [ 6 | [0 , 0, 0], 7 | [1 , 0, 0], 8 | [0 , 1, 0], 9 | [1 , 1, 0], 10 | [0 , 0, 1], 11 | [1 , 0, 1], 12 | [0 , 1, 1], 13 | [1 , 1, 1], 14 | ]; 15 | 16 | export const childDirections = [ 17 | [-1, -1, -1], 18 | [ 1, -1, -1], 19 | [-1, 1, -1], 20 | [ 1, 1, -1], 21 | [-1, -1, 1], 22 | [ 1, -1, 1], 23 | [-1, 1, 1], 24 | [ 1, 1, 1] 25 | ]; 26 | 27 | const i32 = new Int32Array(1); 28 | const f32 = new Float32Array(i32.buffer, 0, 1); 29 | 30 | export function rgba(r: number, g: number, b: number, a: number): number { 31 | i32[0] = (a << 24 | b << 16 | g << 8 | r) & 0xfeffffff; 32 | return f32[0]; 33 | 34 | } -------------------------------------------------------------------------------- /src/octree/worker/block-generator.ts: -------------------------------------------------------------------------------- 1 | import {childDirections, map3D1D} from "../util"; 2 | import {expose} from "threads/worker"; 3 | 4 | function createBlock(out: Float32Array, info, index: number): number { 5 | if (info.node.children) { 6 | for (const childID in info.node.children) { 7 | const childDirection = childDirections[childID]; 8 | const childSize = info.size / 2; 9 | const childInfo = { 10 | node: info.node.children[childID], 11 | size: childSize, 12 | depth: info.depth + 1, 13 | chunkPosition: info.chunkPosition, 14 | position: [ 15 | info.position[0] + childDirection[0] * childSize, 16 | info.position[1] + childDirection[1] * childSize, 17 | info.position[2] + childDirection[2] * childSize 18 | ] 19 | }; 20 | index = createBlock(out, childInfo, index); 21 | } 22 | return index; 23 | 24 | } else { 25 | 26 | 27 | if (info.node.data === 0) { 28 | return index; 29 | } 30 | 31 | const offset = info.size; 32 | 33 | const relPositionStart = [ 34 | (info.position[0] - offset + 0.5) * 1024, 35 | (info.position[1] - offset + 0.5) * 1024, 36 | (info.position[2] - offset + 0.5) * 1024 37 | ]; 38 | 39 | const relPositionEnd = [ 40 | (info.position[0] + offset + 0.5) * 1024, 41 | (info.position[1] + offset + 0.5) * 1024, 42 | (info.position[2] + offset + 0.5) * 1024 43 | ]; 44 | 45 | // top 1 46 | out[index++] = info.position[0] - offset; 47 | out[index++] = info.position[1] - offset; 48 | out[index++] = info.position[2] + offset; 49 | out[index++] = offset; 50 | 51 | return index; 52 | } 53 | } 54 | 55 | const worker = { 56 | 57 | work(chunk: string, mesh?) { 58 | const master = JSON.parse(chunk); 59 | if (!mesh) { 60 | console.log("created new buffer") 61 | } 62 | const b = mesh ? mesh : new SharedArrayBuffer(16777216 * 3 * 4); 63 | const f32 = new Float32Array(b); 64 | const info = { 65 | depth: 0, 66 | position: [0, 0, 0], 67 | chunkPosition: master.id, 68 | size: 0.5, 69 | node: master.tree 70 | }; 71 | 72 | const f32Count = createBlock(f32, info, 0); 73 | 74 | return { 75 | elements: f32Count / 4, 76 | data: f32.buffer 77 | }; 78 | } 79 | }; 80 | 81 | export type BlockGeneratorWorker = typeof worker; 82 | 83 | expose(worker); -------------------------------------------------------------------------------- /src/octree/worker/mesh-generator.ts: -------------------------------------------------------------------------------- 1 | import {TraversalInfo} from "./node"; 2 | import {childDirections, map3D1D, rgba} from "../util"; 3 | import {expose, Transfer} from "threads/worker"; 4 | 5 | 6 | 7 | 8 | const colorMap = { 9 | 1: rgba(255, 255, 255, 0), 10 | 2: rgba(244, 67, 54, 0), 11 | 3: rgba(76, 175, 80, 0), 12 | 4: rgba(33, 150, 243, 0), 13 | 5: rgba(255, 152, 0, 0), 14 | 6: rgba(233, 30, 99, 0), 15 | 7: rgba(128, 128, 128, 0), 16 | 8: rgba(64, 64, 64, 0), 17 | 9: rgba(32, 32, 32, 0), 18 | 10: rgba(255, 255, 255, 255) 19 | } 20 | 21 | export function createMesh ( 22 | out: Float32Array, 23 | rt: Float32Array, 24 | colors: Float32Array, 25 | info, 26 | index: {v: number, rt: number, c: number} 27 | ): {v: number, rt: number, c: number} { 28 | if (info.node.children) { 29 | for (const childID in info.node.children) { 30 | const childDirection = childDirections[childID]; 31 | const childSize = info.size / 2; 32 | const childInfo = { 33 | node: info.node.children[childID], 34 | size: childSize, 35 | depth: info.depth + 1, 36 | chunkPosition: info.chunkPosition, 37 | position: [ 38 | info.position[0] + childDirection[0] * childSize, 39 | info.position[1] + childDirection[1] * childSize, 40 | info.position[2] + childDirection[2] * childSize 41 | ] 42 | }; 43 | index = createMesh(out, rt, colors, childInfo, index); 44 | } 45 | return index; 46 | 47 | } else { 48 | 49 | 50 | if (info.node.data === 0) { 51 | return index; 52 | } 53 | 54 | const offset = info.size; 55 | 56 | const relPositionStart = [ 57 | (info.position[0] - offset + 0.5) * 1024, 58 | (info.position[1] - offset + 0.5) * 1024, 59 | (info.position[2] - offset + 0.5) * 1024 60 | ]; 61 | 62 | const relPositionEnd = [ 63 | (info.position[0] + offset + 0.5) * 1024, 64 | (info.position[1] + offset + 0.5) * 1024, 65 | (info.position[2] + offset + 0.5) * 1024 66 | ]; 67 | 68 | // Welcome prototype to hell. 69 | 70 | // top 1 71 | out[index.v++] = info.position[0] - offset; 72 | out[index.v++] = info.position[1] - offset; 73 | out[index.v++] = info.position[2] + offset; 74 | out[index.v++] = 0; 75 | out[index.v++] = 0; 76 | out[index.v++] = 1; 77 | out[index.v++] = colorMap[info.node.data]; 78 | 79 | out[index.v++] = info.position[0] + offset; 80 | out[index.v++] = info.position[1] - offset; 81 | out[index.v++] = info.position[2] + offset; 82 | out[index.v++] = 0; 83 | out[index.v++] = 0; 84 | out[index.v++] = 1; 85 | out[index.v++] = colorMap[info.node.data]; 86 | 87 | out[index.v++] = info.position[0] + offset; 88 | out[index.v++] = info.position[1] + offset; 89 | out[index.v++] = info.position[2] + offset; 90 | out[index.v++] = 0; 91 | out[index.v++] = 0; 92 | out[index.v++] = 1; 93 | out[index.v++] = colorMap[info.node.data]; 94 | 95 | // top 2 96 | out[index.v++] = info.position[0] - offset; 97 | out[index.v++] = info.position[1] - offset; 98 | out[index.v++] = info.position[2] + offset; 99 | out[index.v++] = 0; 100 | out[index.v++] = 0; 101 | out[index.v++] = 1; 102 | out[index.v++] = colorMap[info.node.data]; 103 | 104 | out[index.v++] = info.position[0] + offset; 105 | out[index.v++] = info.position[1] + offset; 106 | out[index.v++] = info.position[2] + offset; 107 | out[index.v++] = 0; 108 | out[index.v++] = 0; 109 | out[index.v++] = 1; 110 | out[index.v++] = colorMap[info.node.data]; 111 | 112 | out[index.v++] = info.position[0] - offset; 113 | out[index.v++] = info.position[1] + offset; 114 | out[index.v++] = info.position[2] + offset; 115 | out[index.v++] = 0; 116 | out[index.v++] = 0; 117 | out[index.v++] = 1; 118 | out[index.v++] = colorMap[info.node.data]; 119 | 120 | 121 | // bottom 1 122 | 123 | out[index.v++] = info.position[0] + offset; 124 | out[index.v++] = info.position[1] - offset; 125 | out[index.v++] = info.position[2] - offset; 126 | out[index.v++] = 0; 127 | out[index.v++] = 0; 128 | out[index.v++] = -1; 129 | out[index.v++] = colorMap[info.node.data]; 130 | 131 | out[index.v++] = info.position[0] - offset; 132 | out[index.v++] = info.position[1] - offset; 133 | out[index.v++] = info.position[2] - offset; 134 | out[index.v++] = 0; 135 | out[index.v++] = 0; 136 | out[index.v++] = -1; 137 | out[index.v++] = colorMap[info.node.data]; 138 | 139 | out[index.v++] = info.position[0] + offset; 140 | out[index.v++] = info.position[1] + offset; 141 | out[index.v++] = info.position[2] - offset; 142 | out[index.v++] = 0; 143 | out[index.v++] = 0; 144 | out[index.v++] = -1; 145 | out[index.v++] = colorMap[info.node.data]; 146 | 147 | // bottom 2 148 | out[index.v++] = info.position[0] + offset; 149 | out[index.v++] = info.position[1] + offset; 150 | out[index.v++] = info.position[2] - offset; 151 | out[index.v++] = 0; 152 | out[index.v++] = 0; 153 | out[index.v++] = -1; 154 | out[index.v++] = colorMap[info.node.data]; 155 | 156 | out[index.v++] = info.position[0] - offset; 157 | out[index.v++] = info.position[1] - offset; 158 | out[index.v++] = info.position[2] - offset; 159 | out[index.v++] = 0; 160 | out[index.v++] = 0; 161 | out[index.v++] = -1; 162 | out[index.v++] = colorMap[info.node.data]; 163 | 164 | out[index.v++] = info.position[0] - offset; 165 | out[index.v++] = info.position[1] + offset; 166 | out[index.v++] = info.position[2] - offset; 167 | out[index.v++] = 0; 168 | out[index.v++] = 0; 169 | out[index.v++] = -1; 170 | out[index.v++] = colorMap[info.node.data]; 171 | 172 | // Front 1 173 | out[index.v++] = info.position[0] - offset; 174 | out[index.v++] = info.position[1] + offset; 175 | out[index.v++] = info.position[2] - offset; 176 | out[index.v++] = 0; 177 | out[index.v++] = 1; 178 | out[index.v++] = 0; 179 | out[index.v++] = colorMap[info.node.data]; 180 | 181 | out[index.v++] = info.position[0] - offset; 182 | out[index.v++] = info.position[1] + offset; 183 | out[index.v++] = info.position[2] + offset; 184 | out[index.v++] = 0; 185 | out[index.v++] = 1; 186 | out[index.v++] = 0; 187 | out[index.v++] = colorMap[info.node.data]; 188 | 189 | out[index.v++] = info.position[0] + offset; 190 | out[index.v++] = info.position[1] + offset; 191 | out[index.v++] = info.position[2] - offset; 192 | out[index.v++] = 0; 193 | out[index.v++] = 1; 194 | out[index.v++] = 0; 195 | out[index.v++] = colorMap[info.node.data]; 196 | 197 | // Front 2 198 | out[index.v++] = info.position[0] - offset; 199 | out[index.v++] = info.position[1] + offset; 200 | out[index.v++] = info.position[2] + offset; 201 | out[index.v++] = 0; 202 | out[index.v++] = 1; 203 | out[index.v++] = 0; 204 | out[index.v++] = colorMap[info.node.data]; 205 | 206 | out[index.v++] = info.position[0] + offset; 207 | out[index.v++] = info.position[1] + offset; 208 | out[index.v++] = info.position[2] + offset; 209 | out[index.v++] = 0; 210 | out[index.v++] = 1; 211 | out[index.v++] = 0; 212 | out[index.v++] = colorMap[info.node.data]; 213 | 214 | out[index.v++] = info.position[0] + offset; 215 | out[index.v++] = info.position[1] + offset; 216 | out[index.v++] = info.position[2] - offset; 217 | out[index.v++] = 0; 218 | out[index.v++] = 1; 219 | out[index.v++] = 0; 220 | out[index.v++] = colorMap[info.node.data]; 221 | 222 | // back 1 223 | out[index.v++] = info.position[0] - offset; 224 | out[index.v++] = info.position[1] - offset; 225 | out[index.v++] = info.position[2] + offset; 226 | out[index.v++] = 0; 227 | out[index.v++] = -1; 228 | out[index.v++] = 0; 229 | out[index.v++] = colorMap[info.node.data]; 230 | 231 | out[index.v++] = info.position[0] - offset; 232 | out[index.v++] = info.position[1] - offset; 233 | out[index.v++] = info.position[2] - offset; 234 | out[index.v++] = 0; 235 | out[index.v++] = -1; 236 | out[index.v++] = 0; 237 | out[index.v++] = colorMap[info.node.data]; 238 | 239 | out[index.v++] = info.position[0] + offset; 240 | out[index.v++] = info.position[1] - offset; 241 | out[index.v++] = info.position[2] - offset; 242 | out[index.v++] = 0; 243 | out[index.v++] = -1; 244 | out[index.v++] = 0; 245 | out[index.v++] = colorMap[info.node.data]; 246 | 247 | // back 2 248 | out[index.v++] = info.position[0] + offset; 249 | out[index.v++] = info.position[1] - offset; 250 | out[index.v++] = info.position[2] + offset; 251 | out[index.v++] = 0; 252 | out[index.v++] = -1; 253 | out[index.v++] = 0; 254 | out[index.v++] = colorMap[info.node.data]; 255 | 256 | out[index.v++] = info.position[0] - offset; 257 | out[index.v++] = info.position[1] - offset; 258 | out[index.v++] = info.position[2] + offset; 259 | out[index.v++] = 0; 260 | out[index.v++] = -1; 261 | out[index.v++] = 0; 262 | out[index.v++] = colorMap[info.node.data]; 263 | 264 | out[index.v++] = info.position[0] + offset; 265 | out[index.v++] = info.position[1] - offset; 266 | out[index.v++] = info.position[2] - offset; 267 | out[index.v++] = 0; 268 | out[index.v++] = -1; 269 | out[index.v++] = 0; 270 | out[index.v++] = colorMap[info.node.data]; 271 | 272 | 273 | // right 1 274 | out[index.v++] = info.position[0] + offset; 275 | out[index.v++] = info.position[1] - offset; 276 | out[index.v++] = info.position[2] - offset; 277 | out[index.v++] = 1; 278 | out[index.v++] = 0; 279 | out[index.v++] = 0; 280 | out[index.v++] = colorMap[info.node.data]; 281 | 282 | out[index.v++] = info.position[0] + offset; 283 | out[index.v++] = info.position[1] + offset; 284 | out[index.v++] = info.position[2] - offset; 285 | out[index.v++] = 1; 286 | out[index.v++] = 0; 287 | out[index.v++] = 0; 288 | out[index.v++] = colorMap[info.node.data]; 289 | 290 | out[index.v++] = info.position[0] + offset; 291 | out[index.v++] = info.position[1] - offset; 292 | out[index.v++] = info.position[2] + offset; 293 | out[index.v++] = 1; 294 | out[index.v++] = 0; 295 | out[index.v++] = 0; 296 | out[index.v++] = colorMap[info.node.data]; 297 | 298 | // right 2 299 | 300 | out[index.v++] = info.position[0] + offset; 301 | out[index.v++] = info.position[1] + offset; 302 | out[index.v++] = info.position[2] - offset; 303 | out[index.v++] = 1; 304 | out[index.v++] = 0; 305 | out[index.v++] = 0; 306 | out[index.v++] = colorMap[info.node.data]; 307 | 308 | out[index.v++] = info.position[0] + offset; 309 | out[index.v++] = info.position[1] + offset; 310 | out[index.v++] = info.position[2] + offset; 311 | out[index.v++] = 1; 312 | out[index.v++] = 0; 313 | out[index.v++] = 0; 314 | out[index.v++] = colorMap[info.node.data]; 315 | 316 | out[index.v++] = info.position[0] + offset; 317 | out[index.v++] = info.position[1] - offset; 318 | out[index.v++] = info.position[2] + offset; 319 | out[index.v++] = 1; 320 | out[index.v++] = 0; 321 | out[index.v++] = 0; 322 | out[index.v++] = colorMap[info.node.data]; 323 | 324 | // left 1 325 | out[index.v++] = info.position[0] - offset; 326 | out[index.v++] = info.position[1] + offset; 327 | out[index.v++] = info.position[2] - offset; 328 | out[index.v++] = -1; 329 | out[index.v++] = 0; 330 | out[index.v++] = 0; 331 | out[index.v++] = colorMap[info.node.data]; 332 | 333 | out[index.v++] = info.position[0] - offset; 334 | out[index.v++] = info.position[1] - offset; 335 | out[index.v++] = info.position[2] - offset; 336 | out[index.v++] = -1; 337 | out[index.v++] = 0; 338 | out[index.v++] = 0; 339 | out[index.v++] = colorMap[info.node.data]; 340 | 341 | out[index.v++] = info.position[0] - offset; 342 | out[index.v++] = info.position[1] - offset; 343 | out[index.v++] = info.position[2] + offset; 344 | out[index.v++] = -1; 345 | out[index.v++] = 0; 346 | out[index.v++] = 0; 347 | out[index.v++] = colorMap[info.node.data]; 348 | 349 | // left 2 350 | 351 | out[index.v++] = info.position[0] - offset; 352 | out[index.v++] = info.position[1] + offset; 353 | out[index.v++] = info.position[2] + offset; 354 | out[index.v++] = -1; 355 | out[index.v++] = 0; 356 | out[index.v++] = 0; 357 | out[index.v++] = colorMap[info.node.data]; 358 | 359 | out[index.v++] = info.position[0] - offset; 360 | out[index.v++] = info.position[1] + offset; 361 | out[index.v++] = info.position[2] - offset; 362 | out[index.v++] = -1; 363 | out[index.v++] = 0; 364 | out[index.v++] = 0; 365 | out[index.v++] = colorMap[info.node.data]; 366 | 367 | out[index.v++] = info.position[0] - offset; 368 | out[index.v++] = info.position[1] - offset; 369 | out[index.v++] = info.position[2] + offset; 370 | out[index.v++] = -1; 371 | out[index.v++] = 0; 372 | out[index.v++] = 0; 373 | out[index.v++] = colorMap[info.node.data]; 374 | 375 | // top 1 376 | rt[index.rt++] = info.position[0]; 377 | rt[index.rt++] = info.position[1]; 378 | rt[index.rt++] = info.position[2]; 379 | rt[index.rt++] = offset; 380 | 381 | 382 | colors[index.c++] = colorMap[info.node.data]; 383 | 384 | return index; 385 | } 386 | } 387 | 388 | const oc_size = 64 389 | 390 | export function createOctree ( 391 | out: Float32Array, 392 | info, 393 | index128: number 394 | ): number { 395 | let index32 = index128 * 4; 396 | if (info.node.children) { 397 | 398 | for (const childID in info.node.children) { 399 | const childInfo = { 400 | node: info.node.children[childID], 401 | depth: info.depth + 1 402 | }; 403 | 404 | if (childInfo.node.children) { 405 | const childIndex128 = index128 + 8; 406 | out[index32++] = (childIndex128 % oc_size) / oc_size; 407 | out[index32++] = Math.floor(childIndex128 / oc_size) / oc_size; 408 | out[index32++] = info.depth; // empty placeholder 409 | out[index32++] = 0; // type 0 stands for node 410 | index128 = createOctree(out, childInfo, childIndex128); 411 | } else { 412 | out[index32++] = colorMap[childInfo.node.data] ? colorMap[childInfo.node.data] : 0; 413 | out[index32++] = 0; // empty placeholder 414 | out[index32++] = childInfo.depth; // empty placeholder 415 | out[index32++] = 1; // type 1 stands for leaf 416 | } 417 | } 418 | return index128; 419 | } else { 420 | // only if the root has no children 421 | if (info.node.data === 0) { 422 | return index128; 423 | } 424 | 425 | out[index32++] = colorMap[info.node.data]; 426 | out[index32++] = 0; // empty placeholder 427 | out[index32++] = info.depth; // empty placeholder 428 | out[index32++] = 1; // type 1 stands for leaf 429 | 430 | return index128; 431 | } 432 | } 433 | 434 | 435 | const worker = { 436 | 437 | work(id: number[], chunks: string, mesh?, pBlocks?, pColors?, pOctree?) { 438 | const parsed = JSON.parse(chunks); 439 | const master = parsed[map3D1D(id)]; 440 | if (!mesh) { 441 | console.log("created new buffer") 442 | } 443 | const b = mesh ? mesh : new SharedArrayBuffer(16777216 * 3 * 4); 444 | const f32 = new Float32Array(b); 445 | 446 | const blocks = pBlocks ? pBlocks : new SharedArrayBuffer(16 * 16 * 16 * 4 * 4); 447 | const blocks32 = new Float32Array(blocks); 448 | 449 | const octree = pOctree ? pOctree : new SharedArrayBuffer(16 * 16 * 16 * 4 * 4); 450 | const octree32 = new Float32Array(octree); 451 | 452 | const colors = pColors ? pColors : new SharedArrayBuffer(16 * 16 * 16 * 4 * 4); 453 | const colors32 = new Float32Array(colors); 454 | 455 | const info = { 456 | depth: 0, 457 | position: [0, 0, 0], 458 | chunkPosition: id, 459 | size: 0.5, 460 | node: master.tree 461 | }; 462 | 463 | const index = createMesh(f32, blocks32, colors32, info, { v: 0, rt: 0, c: 0 }); 464 | index.v /= 7; 465 | index.rt /= 4; 466 | 467 | 468 | const infoOC = { 469 | depth: 0, 470 | position: [0, 0, 0], 471 | chunkPosition: id, 472 | size: 0.5, 473 | node: master.tree 474 | }; 475 | 476 | const indexOC = createOctree(octree32, infoOC, 0); 477 | 478 | return { 479 | index: index, 480 | indexOC: indexOC, 481 | oc: octree32.buffer, 482 | v: f32.buffer, 483 | rt: blocks32.buffer, 484 | colors: colors32.buffer 485 | }; 486 | } 487 | }; 488 | 489 | export type MeshGeneratorWorker = typeof worker; 490 | 491 | expose(worker); -------------------------------------------------------------------------------- /src/octree/worker/node.ts: -------------------------------------------------------------------------------- 1 | export interface OctreeNode { 2 | data: number; 3 | children?: {[key: number]: OctreeNode}; 4 | } 5 | 6 | export interface TraversalInfo { 7 | size: number, 8 | depth: number, 9 | position?: number[] 10 | node: OctreeNode 11 | } 12 | 13 | export function modify(info: TraversalInfo, p1: number[], p2: number[], value: number) { 14 | // check pre merge 15 | if ( 16 | p1[0] === 0 && p1[1] === 0 && p1[2] === 0 && 17 | p2[0] === (info.size -1) && p2[1] === (info.size -1) && p2[2] === (info.size -1) 18 | ) { 19 | // can pre merged 20 | info.node.data = value; 21 | delete info.node.children; 22 | return 23 | } else { 24 | // update 25 | if (!info.node.children) { 26 | info.node.children = { 27 | 0: { data: info.node.data }, 28 | 1: { data: info.node.data }, 29 | 2: { data: info.node.data }, 30 | 3: { data: info.node.data }, 31 | 4: { data: info.node.data }, 32 | 5: { data: info.node.data }, 33 | 6: { data: info.node.data }, 34 | 7: { data: info.node.data } 35 | }; 36 | } 37 | 38 | // cut update down 39 | const childSize = info.size / 2; 40 | const x0 = lowerRange(p1[0], p2[0], childSize); 41 | const y0 = lowerRange(p1[1], p2[1], childSize); 42 | const z0 = lowerRange(p1[2], p2[2], childSize); 43 | 44 | const x1 = upperRange(p1[0], p2[0], childSize); 45 | const y1 = upperRange(p1[1], p2[1], childSize); 46 | const z1 = upperRange(p1[2], p2[2], childSize); 47 | 48 | const childInfo: TraversalInfo = { 49 | node: undefined, 50 | size: childSize, 51 | depth: info.depth + 1 52 | }; 53 | 54 | if (x0) { 55 | if (y0) { 56 | if (z0) { 57 | // const cIndex = childIndex([0, 0, 0]); 58 | const cIndex = 0; 59 | 60 | childInfo.node = info.node.children[cIndex]; 61 | modify(childInfo, [x0[0], y0[0], z0[0]], [x0[1], y0[1], z0[1]], value); 62 | } 63 | 64 | if (z1) { 65 | // const cIndex = childIndex([0, 0, 1]); 66 | const cIndex = 4; 67 | childInfo.node = info.node.children[cIndex]; 68 | modify(childInfo, [x0[0], y0[0], z1[0]], [x0[1], y0[1], z1[1]], value); 69 | } 70 | } 71 | 72 | if (y1) { 73 | if (z0) { 74 | // const cIndex = childIndex([0, 1, 0]); 75 | const cIndex = 2; 76 | childInfo.node = info.node.children[cIndex]; 77 | modify(childInfo, [x0[0], y1[0], z0[0]], [x0[1], y1[1], z0[1]], value); 78 | } 79 | 80 | if (z1) { 81 | // const cIndex = childIndex([0, 1, 1]); 82 | const cIndex = 6; 83 | childInfo.node = info.node.children[cIndex]; 84 | modify(childInfo, [x0[0], y1[0], z1[0]], [x0[1], y1[1], z1[1]], value); 85 | } 86 | } 87 | } 88 | 89 | if (x1) { 90 | if (y0) { 91 | if (z0) { 92 | // const cIndex = childIndex([1, 0, 0]); 93 | const cIndex = 1; 94 | childInfo.node = info.node.children[cIndex]; 95 | modify(childInfo, [x1[0], y0[0], z0[0]], [x1[1], y0[1], z0[1]], value); 96 | } 97 | 98 | if (z1) { 99 | // const cIndex = childIndex([1, 0, 1]); 100 | const cIndex = 5; 101 | childInfo.node = info.node.children[cIndex]; 102 | modify(childInfo, [x1[0], y0[0], z1[0]], [x1[1], y0[1], z1[1]], value); 103 | } 104 | } 105 | 106 | if (y1) { 107 | if (z0) { 108 | // const cIndex = childIndex([1, 1, 0]); 109 | const cIndex = 3; 110 | childInfo.node = info.node.children[cIndex]; 111 | modify(childInfo, [x1[0], y1[0], z0[0]], [x1[1], y1[1], z0[1]], value); 112 | } 113 | if (z1) { 114 | // const cIndex = childIndex([1, 1, 1]); 115 | const cIndex = 7; 116 | childInfo.node = info.node.children[cIndex]; 117 | modify(childInfo, [x1[0], y1[0], z1[0]], [x1[1], y1[1], z1[1]], value); 118 | } 119 | } 120 | } 121 | 122 | // post merge 123 | const childKeys = Object.keys(info.node.children); 124 | if (childKeys.length === 8) { 125 | let canMerge = true; 126 | let data; 127 | for (const key of childKeys) { 128 | if (info.node.children[key].children || (data = info.node.children[key].data) === info.node.data) { 129 | canMerge = false; 130 | break; 131 | } 132 | } 133 | 134 | if (canMerge) { 135 | info.node.data = data; 136 | delete info.node.children; 137 | } 138 | } 139 | } 140 | } 141 | 142 | function lowerRange(n1, n2, mid) { 143 | if (n1 < mid) { 144 | return [n1, n2 >= mid ? mid -1 : n2] 145 | } 146 | } 147 | 148 | function upperRange(n1, n2, mid) { 149 | if (n2 >= mid) { 150 | return [n1 >= mid ? n1 - mid : 0, n2 - mid] 151 | } 152 | } 153 | 154 | export function getData(info: TraversalInfo, p1: number[]) { 155 | 156 | if (!info.node.children || info.size === 1) { 157 | return info.node.data; 158 | } else { 159 | const childID = [ 160 | p1[0] < info.size ? 0 : 1, 161 | p1[1] < info.size ? 0 : 1, 162 | p1[2] < info.size ? 0 : 1 163 | ]; 164 | 165 | const childInfo: TraversalInfo = { 166 | node: info.node.children[childIndex(childID)], 167 | size: info.size / 2, 168 | depth: info.depth + 1 169 | }; 170 | 171 | return getData(childInfo, p1); 172 | } 173 | } 174 | 175 | export function childIndex(p: number[]): number { 176 | return p[0] + 2 * (p[1] + 2 * p[2]); 177 | } 178 | 179 | -------------------------------------------------------------------------------- /src/render/camera.ts: -------------------------------------------------------------------------------- 1 | import {mat4, vec3} from "gl-matrix"; 2 | import {canvas} from "./context"; 3 | 4 | export class Camera { 5 | 6 | view: mat4 = mat4.create(); 7 | perspective: mat4 = mat4.create(); 8 | 9 | rotX: number = -Math.PI / 2; 10 | rotY: number = 0; 11 | matX: mat4 = mat4.create(); 12 | matY: mat4 = mat4.create(); 13 | matRotation: mat4 = mat4.create(); 14 | matPosition: mat4 = mat4.create(); 15 | 16 | forward: boolean = false; 17 | backward: boolean = false; 18 | left: boolean = false; 19 | right: boolean = false; 20 | speed: boolean = false; 21 | lastUpdateTime: number = Date.now(); 22 | 23 | constructor () { 24 | mat4.translate(this.matPosition, this.matPosition, [0, 0.75, 0]); 25 | mat4.rotateX(this.matX, this.matX, this.rotX); 26 | mat4.mul(this.matRotation, this.matX, this.matY); 27 | 28 | document.addEventListener("click", (event) => { 29 | if (!document.pointerLockElement) { 30 | document.body.requestPointerLock(); 31 | } 32 | }); 33 | 34 | document.addEventListener('mousemove', (event: any) => { 35 | 36 | if (!document.pointerLockElement) { 37 | return 38 | } 39 | 40 | this.rotY += event.movementX * 0.0025; 41 | this.rotX += event.movementY * 0.00251; 42 | 43 | 44 | mat4.identity(this.matX); 45 | mat4.identity(this.matY); 46 | 47 | 48 | if (this.rotX > 0) { 49 | this.rotX = 0; 50 | } 51 | 52 | if (this.rotX < -Math.PI) { 53 | this.rotX = -Math.PI; 54 | } 55 | 56 | if (this.rotY > Math.PI) { 57 | this.rotY -= 2 * Math.PI; 58 | } 59 | 60 | if (this.rotY < -Math.PI) { 61 | this.rotY += 2 * Math.PI; 62 | } 63 | 64 | mat4.rotateX(this.matX, this.matX, this.rotX); 65 | mat4.rotateZ(this.matY, this.matY, this.rotY); 66 | 67 | mat4.mul(this.matRotation, this.matX, this.matY); 68 | }, false); 69 | 70 | // WTF clean up this shit 71 | document.addEventListener('keydown', (element) => { 72 | switch (element.key) { 73 | case "w": 74 | case "W": 75 | this.forward = true; break; 76 | case "s": 77 | case "S": 78 | this.backward = true; break; 79 | case "a": 80 | case "A": 81 | this.left = true; break; 82 | case "d": 83 | case "D": 84 | this.right = true; break; 85 | case "Shift": 86 | this.speed = true; break; 87 | } 88 | }); 89 | 90 | // WTF clean up this shit 91 | document.addEventListener('keyup', (element) => { 92 | switch (element.key) { 93 | case "w": 94 | case "W": 95 | this.forward = false; break; 96 | case "s": 97 | case "S": 98 | this.backward = false; break; 99 | case "a": 100 | case "A": 101 | this.left = false; break; 102 | case "d": 103 | case "D": 104 | this.right = false; break; 105 | case "Shift": 106 | this.speed = false; break; 107 | } 108 | }); 109 | } 110 | 111 | 112 | update() { 113 | const now = Date.now(); 114 | const speed = (now - this.lastUpdateTime) * 0.001 * (this.speed ? 3 : 1); 115 | this.lastUpdateTime = now; 116 | 117 | const ar = canvas.width / canvas.height; 118 | mat4.perspective(this.perspective, 1.5, ar, 0.01, 100); 119 | 120 | let inverseRotation = mat4.create(); 121 | mat4.invert(inverseRotation,this.matRotation); 122 | 123 | 124 | if (this.forward) { 125 | let forward = vec3.fromValues(0 , 0, 0.1 * speed); 126 | vec3.transformMat4(forward, forward, inverseRotation); 127 | mat4.translate(this.matPosition, this.matPosition, forward) 128 | } 129 | 130 | if (this.backward) { 131 | let backward = vec3.fromValues(0 , 0, -0.1 * speed); 132 | vec3.transformMat4(backward, backward, inverseRotation); 133 | mat4.translate(this.matPosition, this.matPosition, backward) 134 | } 135 | 136 | if (this.left) { 137 | let left = vec3.fromValues(0.1 * speed, 0, 0); 138 | vec3.transformMat4(left, left, inverseRotation); 139 | mat4.translate(this.matPosition, this.matPosition, left) 140 | } 141 | 142 | if (this.right) { 143 | let right = vec3.fromValues(-0.1 * speed, 0, 0); 144 | vec3.transformMat4(right, right, inverseRotation); 145 | mat4.translate(this.matPosition, this.matPosition, right) 146 | } 147 | 148 | mat4.mul(this.view, this.matRotation, this.matPosition); 149 | } 150 | 151 | get position(): Float32Array { 152 | let out = vec3.create(); 153 | mat4.getTranslation(out, this.matPosition); 154 | return out; 155 | } 156 | 157 | } -------------------------------------------------------------------------------- /src/render/context.ts: -------------------------------------------------------------------------------- 1 | import {setContext} from "@foxel_fox/glib"; 2 | 3 | export const pixelRatio = window.devicePixelRatio || 1; 4 | export const canvas = document.createElement("canvas"); 5 | 6 | export const gl = canvas.getContext("webgl2", { 7 | antialias: false, 8 | alpha: false, 9 | premultipliedAlpha: false 10 | }) as WebGL2RenderingContext; 11 | setContext(gl); 12 | 13 | gl.getExtension("EXT_color_buffer_float"); 14 | gl.enable(gl.CULL_FACE); 15 | gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); 16 | gl.clearColor(1,1,1,1); 17 | //gl.cullFace(gl.FRONT_FACE); 18 | 19 | 20 | const cursor = document.createElement("div"); 21 | cursor.style.width = "10px"; 22 | cursor.style.height = "20px"; 23 | cursor.style.color = "white"; 24 | cursor.style.position = "absolute"; 25 | cursor.style.margin = "auto"; 26 | cursor.style.left = "0"; 27 | cursor.style.right = "0"; 28 | cursor.style.top = "0"; 29 | cursor.style.bottom = "0"; 30 | cursor.innerText = "+"; 31 | 32 | 33 | canvas.style.width = "100vw"; 34 | canvas.style.height = "100vh"; 35 | canvas.style.display = "block"; 36 | 37 | document.body.style.margin = "0"; 38 | document.body.appendChild(cursor); 39 | document.body.appendChild(canvas); 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/render/loop.ts: -------------------------------------------------------------------------------- 1 | import {PipelineV1} from "./pipeline/v1/pipeline-v1"; 2 | import {canvas, gl, pixelRatio} from "./context"; 3 | import {resizeDrawingBuffer} from "@foxel_fox/glib"; 4 | import GLBench from "gl-bench/dist/gl-bench"; 5 | import {Camera} from "./camera"; 6 | import {PipelineV2} from "./pipeline/v2/pipeline-v2"; 7 | import {generateStartScene} from "../generator/start-scene"; 8 | 9 | 10 | export let bench; 11 | 12 | function resize() { 13 | // Lookup the size the browser is displaying the canvas. 14 | let displayWidth = document.documentElement.clientWidth; 15 | let displayHeight = document.documentElement.clientHeight; 16 | 17 | // Check if the canvas is not the same size. 18 | if ( 19 | canvas.width !== displayWidth * pixelRatio || 20 | canvas.height !== displayHeight * pixelRatio 21 | ) { 22 | canvas.width = displayWidth * pixelRatio; 23 | canvas.height = displayHeight * pixelRatio; 24 | 25 | canvas.style.width = displayWidth + "px"; 26 | canvas.style.height = displayHeight + "px"; 27 | 28 | resizeDrawingBuffer(); 29 | } 30 | } 31 | 32 | export function init() { 33 | return new Promise(resolve => { 34 | bench = new GLBench(gl); 35 | 36 | setTimeout(() => { 37 | resolve(undefined); 38 | }, 16 * 4); 39 | }); 40 | } 41 | 42 | export function start(grid) { 43 | 44 | const camera = new Camera(); 45 | let pipeline = new PipelineV1(grid, camera); 46 | 47 | generateStartScene(grid); 48 | requestAnimationFrame(loop); 49 | 50 | function loop() { 51 | bench.nextFrame(); 52 | bench.begin(); 53 | resize(); 54 | 55 | grid.getNext().then(n => { 56 | if (n) { 57 | pipeline.chunkNode.uploadQueue.push(n); 58 | } 59 | }) 60 | 61 | pipeline.run(); 62 | bench.end(); 63 | requestAnimationFrame(loop); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/render/pipeline/shared-node/denoiser/denoiser.fs.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision lowp float; 4 | precision lowp sampler2D; 5 | 6 | uniform sampler2D tDiffuse; 7 | uniform sampler2D tNormal; 8 | uniform sampler2D tPosition; 9 | 10 | uniform sampler2D tDiffuseL; 11 | uniform sampler2D tNormalL; 12 | uniform sampler2D tPositionL; 13 | uniform sampler2D tRTLightL; 14 | 15 | 16 | uniform sampler2D tRTLight; 17 | uniform sampler2D tRTFiltered; 18 | 19 | uniform mat4 oldMVP; 20 | uniform int rtBlocks; 21 | uniform vec2 sampleSize; 22 | uniform vec2 sampleRTSize; 23 | uniform float reset; 24 | 25 | in vec2 v_texCoord; 26 | 27 | out vec4 outColor; 28 | 29 | 30 | void main() { 31 | 32 | vec2 sampleX = vec2(sampleSize.x,0); 33 | vec2 sampleY = vec2(0, sampleSize.y); 34 | 35 | vec4 d = texture(tDiffuse, v_texCoord); 36 | vec3 n = texture(tNormal, v_texCoord).xyz; 37 | vec4 p = texture(tPosition, v_texCoord); 38 | 39 | 40 | vec4 rtC = texture(tRTLight, v_texCoord) + texture(tRTLight, v_texCoord + sampleX) + texture(tRTLight, v_texCoord - sampleX) + texture(tRTLight, v_texCoord + sampleY) + texture(tRTLight, v_texCoord - sampleY); 41 | rtC /= 5.0; 42 | 43 | vec4 posOld = oldMVP * vec4(p.xyz, 1.0); 44 | vec2 uvOld = (posOld.xy / (posOld.w )) * 0.5 + 0.5; 45 | 46 | 47 | 48 | vec3 nL = (texture(tNormalL, uvOld).xyz + texture(tNormalL, uvOld + sampleX).xyz + texture(tNormalL, uvOld - sampleX).xyz + texture(tNormalL, uvOld + sampleY).xyz + texture(tNormalL, uvOld - sampleY).xyz) / 5.0; 49 | vec4 dL = texture(tDiffuseL, uvOld) * 0.2 + texture(tDiffuseL, uvOld + sampleX) * 0.2 + texture(tDiffuseL, uvOld - sampleX) * 0.2 + texture(tDiffuseL, uvOld + sampleY) * 0.2 + + texture(tDiffuseL, uvOld - sampleY) * 0.2; 50 | vec4 pL = texture(tPositionL, uvOld) * 0.2 + texture(tPositionL, uvOld + sampleX) * 0.2 + texture(tPositionL, uvOld -sampleX) * 0.2 + texture(tPositionL, uvOld + sampleY) * 0.2 + texture(tPositionL, uvOld - sampleY) * 0.2; 51 | // vec3 nL = texture(tNormalL, uvOld).xyz; 52 | // vec4 dL = texture(tDiffuseL, uvOld); 53 | // vec4 pL = texture(tPositionL, uvOld); 54 | //vec4 pL = texture(tPositionL, uvOld); 55 | vec4 rtL = texture(tRTLightL, v_texCoord) * 0.2 + texture(tRTLightL, v_texCoord + sampleX) * 0.2 + texture(tRTLightL, v_texCoord - sampleX) * 0.2 + texture(tRTLightL, v_texCoord + sampleY) * 0.2 + texture(tRTLightL, v_texCoord - sampleY) * 0.2; 56 | 57 | vec4 sum = texture(tRTFiltered, uvOld) + texture(tRTFiltered, uvOld + sampleX) + texture(tRTFiltered, uvOld - sampleX) + texture(tRTFiltered, uvOld - sampleY) + texture(tRTFiltered, uvOld + sampleY); 58 | //vec4 sum = texture(tRTFiltered, uvOld); 59 | sum /= 5.0; 60 | 61 | if (sum.w >= 32.0 && (reset < 1.0 || sum.w >= 64.0)) { 62 | // if (sum.w >= 16.0) { 63 | sum = vec4(sum.rgb / 1.5, sum.w / 1.5); 64 | } 65 | 66 | // TODO 67 | if (abs(uvOld.x -0.5) > 0.5 || abs(uvOld.y-0.5) > 0.5 || distance(d.xyz, dL.xyz) > 0.01 || distance(n, nL) > 0.005 || distance(p.xyz, pL.xyz) > 0.005) { 68 | 69 | 70 | sum = vec4(sum.rgb / 2.0, sum.w / 2.0); 71 | 72 | 73 | //rtC = texture(tRTLightL, v_texCoord); 74 | //sum = vec4(rtL.rgb, 1.0); 75 | } 76 | 77 | 78 | if (length(n.xyz) < 0.1 || length(n.xyz) > 1.1 || length(nL.xyz) < 0.1 || length(nL.xyz) > 1.1) { 79 | // cursor 80 | 81 | 82 | sum = vec4(rtC.rgb, 1.0); 83 | } 84 | 85 | 86 | outColor = sum + vec4(clamp(rtC.rgb, sum.rgb / sum.w - vec3(0.5),sum.rgb / sum.w + vec3(0.5) ), 1.0) ; 87 | //outColor = vec4(rtC.xyz, 1); 88 | 89 | 90 | //outColor.x = texture(tPositionL, uvOld).w; 91 | //outColor.x = p.w; 92 | //outColor = vec4(1,0,0,1);//texture(tPosition, v_texCoord); 93 | 94 | } 95 | 96 | -------------------------------------------------------------------------------- /src/render/pipeline/shared-node/denoiser/denoiser.ts: -------------------------------------------------------------------------------- 1 | import {FrameBuffer, Quad, Shader, SimpleNode, Texture} from "@foxel_fox/glib"; 2 | import {canvas, gl} from "../../../context"; 3 | import { Camera } from "../../../camera"; 4 | 5 | interface DenoiserRequirement { 6 | oldMVP 7 | } 8 | 9 | export class Denoiser extends SimpleNode { 10 | 11 | reset = 0; 12 | 13 | constructor ( 14 | private requirement: DenoiserRequirement, 15 | private diffuse: Texture, 16 | private normal: Texture, 17 | private position: Texture, 18 | private albedo: Texture, 19 | private camera: Camera, 20 | ) { 21 | super(new Shader(require("./denoiser.vs.glsl"), require("./denoiser.fs.glsl")), new Quad() as {}); 22 | } 23 | 24 | init(): void { 25 | const output = new Texture(undefined, undefined, null, gl.RGBA32F, gl.RGBA, gl.FLOAT); 26 | 27 | this.frameBuffer = new FrameBuffer([output], true, false); 28 | 29 | document.addEventListener("click", (e) => { 30 | this.reset = 0; 31 | }); 32 | 33 | document.addEventListener("mousemove", (e) => { 34 | if(document.pointerLockElement) { 35 | this.reset = 0; 36 | } 37 | }); 38 | } 39 | 40 | run(): void { 41 | 42 | if (this.camera.backward || this.camera.forward || this.camera.left || this.camera.right) { 43 | this.reset = 0; 44 | } 45 | 46 | 47 | this.frameBuffer.bind(); 48 | gl.viewport(0, 0, canvas.width, canvas.height); 49 | 50 | gl.useProgram(this.shader.program); 51 | 52 | gl.activeTexture(gl.TEXTURE0); 53 | gl.uniform1i(this.shader.getUniformLocation("tDiffuse"), 0); 54 | gl.bindTexture(gl.TEXTURE_2D, this.diffuse.webGLTexture); 55 | 56 | gl.activeTexture(gl.TEXTURE1); 57 | gl.uniform1i(this.shader.getUniformLocation("tNormal"), 1); 58 | gl.bindTexture(gl.TEXTURE_2D, this.normal.webGLTexture); 59 | 60 | gl.activeTexture(gl.TEXTURE2); 61 | gl.uniform1i(this.shader.getUniformLocation("tPosition"), 2); 62 | gl.bindTexture(gl.TEXTURE_2D, this.position.webGLTexture); 63 | 64 | gl.activeTexture(gl.TEXTURE3); 65 | gl.uniform1i(this.shader.getUniformLocation("tRTLight"), 3); 66 | gl.bindTexture(gl.TEXTURE_2D, this.albedo.webGLTexture); 67 | 68 | gl.activeTexture(gl.TEXTURE4); 69 | gl.uniform1i(this.shader.getUniformLocation("tRTFiltered"), 4); 70 | gl.bindTexture(gl.TEXTURE_2D, this.frameBuffer.textures[0].webGLTexture); 71 | 72 | gl.activeTexture(gl.TEXTURE5); 73 | gl.uniform1i(this.shader.getUniformLocation("tDiffuseL"), 5); 74 | gl.bindTexture(gl.TEXTURE_2D, this.diffuse.webGLTexture2); 75 | 76 | gl.activeTexture(gl.TEXTURE6); 77 | gl.uniform1i(this.shader.getUniformLocation("tNormalL"), 6); 78 | gl.bindTexture(gl.TEXTURE_2D, this.normal.webGLTexture2); 79 | 80 | gl.activeTexture(gl.TEXTURE8); 81 | gl.uniform1i(this.shader.getUniformLocation("tPositionL"), 8); 82 | gl.bindTexture(gl.TEXTURE_2D, this.position.webGLTexture2); 83 | 84 | gl.activeTexture(gl.TEXTURE9); 85 | gl.uniform1i(this.shader.getUniformLocation("tRTLightL"), 9); 86 | gl.bindTexture(gl.TEXTURE_2D, this.albedo.webGLTexture2); 87 | 88 | 89 | gl.uniform2f(this.shader.getUniformLocation("sampleSize"), 1 / canvas.width, 1 / canvas.height); 90 | 91 | 92 | gl.uniform2f(this.shader.getUniformLocation("sampleRTSize"), 1 / this.albedo.x, 1 / this.albedo.y); 93 | 94 | gl.uniform1f(this.shader.getUniformLocation("reset"), this.reset); 95 | gl.uniformMatrix4fv(this.shader.getUniformLocation("oldMVP"), false, this.requirement.oldMVP); 96 | 97 | gl.bindVertexArray(this.vao); 98 | gl.drawArrays(gl.TRIANGLES, 0, 6); 99 | this.frameBuffer.flip(); 100 | this.reset += 0.01; 101 | if (this.reset > 1) { 102 | this.reset = 1; 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /src/render/pipeline/shared-node/denoiser/denoiser.vs.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | in vec4 position; 4 | in vec2 a_texCoord; 5 | 6 | out vec2 v_texCoord; 7 | 8 | void main() { 9 | gl_Position = position; 10 | v_texCoord = a_texCoord; 11 | } -------------------------------------------------------------------------------- /src/render/pipeline/shared-node/edit/edit-node.fs.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision mediump float; 3 | 4 | in vec4 v_color; 5 | 6 | layout(location = 0) out vec4 f_color; 7 | layout(location = 1) out vec3 f_normal; 8 | layout(location = 2) out vec3 f_position; 9 | 10 | 11 | void main() { 12 | f_color = v_color; 13 | f_normal = vec3(1.0, 1.0, 1.0); 14 | f_position = vec3(0.0, 0.0, 0.0); 15 | } -------------------------------------------------------------------------------- /src/render/pipeline/shared-node/edit/edit-node.ts: -------------------------------------------------------------------------------- 1 | import {ArrayBuffer, Shader, SimpleNode, Texture} from "@foxel_fox/glib"; 2 | import {canvas, gl} from "../../../context"; 3 | import {mat4} from "gl-matrix"; 4 | import {Camera} from "../../../camera"; 5 | import {OctreeGrid} from "../../../../octree/grid"; 6 | 7 | 8 | const keyToDataMap = { 9 | "Digit1": 1, 10 | "Digit2": 2, 11 | "Digit3": 3, 12 | "Digit4": 4, 13 | "Digit5": 5, 14 | "Digit6": 6, 15 | "Digit7": 7, 16 | "Digit8": 8, 17 | "Digit9": 9, 18 | "Digit0": 10, 19 | }; 20 | 21 | export class EditNode extends SimpleNode { 22 | 23 | readPixelPosition = new Float32Array(4); 24 | readPixelNormal = new Float32Array(4); 25 | 26 | size = 64; 27 | scale = 0.0625; 28 | 29 | activeData: number = 1; 30 | 31 | constructor ( 32 | private position: Texture, 33 | private camera: Camera, 34 | private grid: OctreeGrid 35 | ) { 36 | super(new Shader(require("./edit-node.vs.glsl"), require("./edit-node.fs.glsl")), { 37 | position: new ArrayBuffer( [ 38 | -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 39 | -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 40 | -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, 41 | 42 | 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 43 | 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 44 | 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 45 | 46 | -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 47 | -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 48 | -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 49 | 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 50 | 51 | -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 52 | 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 53 | 54 | ], 3, gl.FLOAT) 55 | }); 56 | } 57 | 58 | init() { 59 | document.addEventListener("wheel", (e) => { 60 | if (e.deltaY > 0) { 61 | this.size *= 2; 62 | } else { 63 | this.size /= 2; 64 | } 65 | 66 | if (this.size > 512) { 67 | this.size = 512; 68 | } 69 | 70 | if (this.size < 1) { 71 | this.size = 1; 72 | } 73 | 74 | this.scale = this.size / 1024; 75 | }); 76 | 77 | 78 | document.addEventListener("click", (e) => { 79 | if (document.pointerLockElement) { 80 | 81 | 82 | const start = [this.readPixelPosition[0] * 1024 + 1024 * 0.5 - this.size * 0.5, this.readPixelPosition[1] * 1024 + 1024 * 0.5 - this.size * 0.5,this.readPixelPosition[2] * 1024 + 1024 * 0.5 - this.size * 0.5]; 83 | const end = [start[0] + this.size -1, start[1] + this.size -1, start[2] + this.size -1]; 84 | 85 | // console.log(start) 86 | switch (e.button) { 87 | case 0: 88 | this.grid.modify([ 89 | start[0] + this.size * this.readPixelNormal[0] * -1, 90 | start[1] + this.size * this.readPixelNormal[1] * -1, 91 | start[2] + this.size * this.readPixelNormal[2] * -1, 92 | ], [ 93 | end[0] + this.size * this.readPixelNormal[0] * -1, 94 | end[1] + this.size * this.readPixelNormal[1] * -1, 95 | end[2] + this.size * this.readPixelNormal[2] * -1, 96 | ], 0); 97 | 98 | break; 99 | 100 | case 2: 101 | this.grid.modify(start, end, this.activeData); 102 | break; 103 | } 104 | 105 | 106 | } 107 | }); 108 | 109 | document.addEventListener("keyup", (e) => { 110 | if (keyToDataMap[e.code]) { 111 | this.activeData = keyToDataMap[e.code]; 112 | } 113 | }); 114 | } 115 | 116 | handleUserInput() { 117 | gl.readBuffer(gl.COLOR_ATTACHMENT1); 118 | gl.readPixels(canvas.width / 2, canvas.height / 2 , 1, 1, gl.RGBA, gl.FLOAT, this.readPixelNormal); 119 | 120 | // Fix for Nvidia's unprecition 121 | this.readPixelNormal[0] = Math.round(this.readPixelNormal[0]) 122 | this.readPixelNormal[1] = Math.round(this.readPixelNormal[1]) 123 | this.readPixelNormal[2] = Math.round(this.readPixelNormal[2]) 124 | 125 | gl.readBuffer(gl.COLOR_ATTACHMENT2); 126 | gl.readPixels(canvas.width / 2, canvas.height / 2 , 1, 1, gl.RGBA, gl.FLOAT, this.readPixelPosition); 127 | 128 | this.readPixelPosition[0] = Math.round(this.readPixelPosition[0] * 1024) / 1024 129 | this.readPixelPosition[1] = Math.round(this.readPixelPosition[1] * 1024) / 1024 130 | this.readPixelPosition[2] = Math.round(this.readPixelPosition[2] * 1024) / 1024 131 | 132 | // Fix for Nvidia's unprecition 133 | let cx = Math.floor(Math.floor(this.readPixelPosition[0] * 1024) / this.size ) * this.size; 134 | let cy = Math.floor(Math.floor(this.readPixelPosition[1] * 1024) / this.size ) * this.size; 135 | let cz = Math.floor(Math.floor(this.readPixelPosition[2] * 1024) / this.size ) * this.size; 136 | 137 | // grid snapping 138 | let x = cx - 0.5 * this.size * (Math.abs(this.readPixelNormal[0]) -1) + this.readPixelNormal[0] * 0.5 * this.size; 139 | let y = cy - 0.5 * this.size * (Math.abs(this.readPixelNormal[1]) -1) + this.readPixelNormal[1] * 0.5 * this.size; 140 | let z = cz - 0.5 * this.size * (Math.abs(this.readPixelNormal[2]) -1) + this.readPixelNormal[2] * 0.5 * this.size; 141 | 142 | 143 | this.readPixelPosition[0] = x / 1024; 144 | this.readPixelPosition[1] = y / 1024; 145 | this.readPixelPosition[2] = z / 1024; 146 | 147 | 148 | } 149 | 150 | run() { 151 | 152 | gl.useProgram(this.shader.program); 153 | 154 | this.handleUserInput(); 155 | 156 | const matrix = mat4.create(); 157 | 158 | 159 | mat4.fromTranslation(matrix, [ 160 | this.readPixelPosition[0] - this.scale * this.readPixelNormal[0], 161 | this.readPixelPosition[1] - this.scale * this.readPixelNormal[1], 162 | this.readPixelPosition[2] - this.scale * this.readPixelNormal[2] 163 | ]); 164 | 165 | mat4.scale(matrix, matrix, [this.scale, this.scale, this.scale]); 166 | 167 | let mvp = mat4.create(); 168 | 169 | mat4.identity(mvp); 170 | mat4.mul(mvp, this.camera.view, matrix); 171 | mat4.mul(mvp, this.camera.perspective, mvp); 172 | gl.uniformMatrix4fv(this.shader.getUniformLocation("mvp"), false, mvp); 173 | 174 | gl.useProgram(this.shader.program); 175 | gl.bindVertexArray(this.vao); 176 | 177 | gl.enable(gl.BLEND); 178 | 179 | gl.drawArrays(gl.LINES, 0, 24); 180 | 181 | gl.disable(gl.BLEND); 182 | // gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); 183 | } 184 | } -------------------------------------------------------------------------------- /src/render/pipeline/shared-node/edit/edit-node.vs.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | uniform mat4 mvp; 4 | 5 | in vec4 position; 6 | out vec4 v_color; 7 | 8 | void main() { 9 | gl_Position = mvp * position; 10 | gl_Position.z = gl_Position.z -0.0001; 11 | v_color = vec4(1.0); 12 | } -------------------------------------------------------------------------------- /src/render/pipeline/shared-node/output/output.fs.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | uniform sampler2D tFinal; 5 | in vec2 v_texCoord; 6 | out vec4 outColor; 7 | 8 | 9 | 10 | void main() { 11 | 12 | float gamma = 1.5; 13 | vec4 sum = texture(tFinal, v_texCoord); 14 | outColor.rgb = pow(0.95 + log(sum.rgb / sum.w) / 4.0, vec3(gamma)); 15 | //outColor.rgb = texture(tFinal, v_texCoord).rgb; 16 | } 17 | -------------------------------------------------------------------------------- /src/render/pipeline/shared-node/output/output.ts: -------------------------------------------------------------------------------- 1 | import {Quad, Shader, SimpleNode} from "@foxel_fox/glib"; 2 | import {ChunkNode} from "../../v1/node/chunk-node/chunk-node"; 3 | import {RTLightNode} from "../../v1/node/rt-light/rt-light"; 4 | import {canvas, gl} from "../../../context"; 5 | import {mat4} from "gl-matrix"; 6 | import {Denoiser} from "../denoiser/denoiser"; 7 | 8 | export class OutputNode extends SimpleNode { 9 | 10 | constructor ( 11 | private output: Denoiser 12 | ) { 13 | super(new Shader(require("./output.vs.glsl"), require("./output.fs.glsl")), new Quad() as {}); 14 | } 15 | 16 | init(): void { 17 | 18 | } 19 | 20 | run(): void { 21 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 22 | gl.viewport(0, 0, canvas.width, canvas.height); 23 | 24 | gl.useProgram(this.shader.program); 25 | 26 | gl.activeTexture(gl.TEXTURE0); 27 | gl.uniform1i(this.shader.getUniformLocation("tFinal"), 0); 28 | gl.bindTexture(gl.TEXTURE_2D, this.output.frameBuffer.textures[0].webGLTexture); 29 | 30 | 31 | gl.bindVertexArray(this.vao); 32 | gl.drawArrays(gl.TRIANGLES, 0, 6); 33 | 34 | } 35 | } -------------------------------------------------------------------------------- /src/render/pipeline/shared-node/output/output.vs.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | in vec4 position; 4 | in vec2 a_texCoord; 5 | 6 | out vec2 v_texCoord; 7 | 8 | void main() { 9 | gl_Position = position; 10 | v_texCoord = a_texCoord; 11 | } -------------------------------------------------------------------------------- /src/render/pipeline/v1/node/chunk-node/chunk-node.fs.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision highp float; 4 | 5 | in vec4 v_color; 6 | in vec3 v_normal; 7 | in vec4 v_position; 8 | in vec3 wire; 9 | 10 | layout(location = 0) out vec4 f_color; 11 | layout(location = 1) out vec3 f_normal; 12 | layout(location = 2) out vec4 f_position; 13 | 14 | 15 | float edgeFactor(){ 16 | vec3 d = fwidth(wire); 17 | vec3 a3 = smoothstep(vec3(0.0), d * 1.5, wire); 18 | return min(min(a3.x, a3.y), a3.z); 19 | } 20 | 21 | void main() { 22 | 23 | // wireframe 24 | // vec3 wireColor = v_color.rgb + 0.75; 25 | // f_color.rgb = mix(wireColor , v_color.rgb, edgeFactor()); 26 | 27 | 28 | f_color = v_color; 29 | f_normal = v_normal; 30 | f_position = v_position; 31 | } -------------------------------------------------------------------------------- /src/render/pipeline/v1/node/chunk-node/chunk-node.ts: -------------------------------------------------------------------------------- 1 | import {Shader, ArrayBufferNative, FrameBuffer, Texture} from "@foxel_fox/glib"; 2 | import {gl} from "../../../../context"; 3 | import {mat4, vec3} from "gl-matrix"; 4 | import {Camera} from "../../../../camera"; 5 | import {OctreeGrid} from "../../../../../octree/grid"; 6 | import {map3D1D} from "../../../../../octree/util"; 7 | import {VoxelsOnGPU} from "../../../../../octree/chunk"; 8 | 9 | interface Model { 10 | vao: WebGLVertexArrayObject 11 | position: ArrayBufferNative 12 | matrix: mat4 13 | vertexCount: number 14 | } 15 | 16 | export class ChunkNode { 17 | 18 | shader: Shader; 19 | frameBuffer!: FrameBuffer; 20 | models: { [key: number]: Model } = {}; 21 | uploadQueue = []; 22 | chunks: Texture; 23 | colors: Texture; 24 | chunkRTBlocks: number = 0; 25 | oldMVP: mat4; 26 | currentMVP: mat4 = mat4.create(); 27 | 28 | constructor ( 29 | private camera: Camera, 30 | private grid: OctreeGrid 31 | ) { 32 | this.shader = new Shader( 33 | require("./chunk-node.vs.glsl"), 34 | require("./chunk-node.fs.glsl") 35 | ); 36 | } 37 | 38 | init(): void { 39 | const output = new Texture(undefined, undefined, null, undefined, undefined, undefined, gl.LINEAR); 40 | const normal = new Texture(undefined, undefined, null, gl.RGBA16F, gl.RGBA, gl.FLOAT, gl.LINEAR); 41 | const position = new Texture(undefined, undefined, null, gl.RGBA32F, gl.RGBA, gl.FLOAT); 42 | this.chunks = new Texture(4096, 1, undefined, gl.RGBA16F, gl.RGBA, gl.FLOAT); 43 | this.colors = new Texture(4096, 1); 44 | 45 | this.frameBuffer = new FrameBuffer([output, normal, position], true, true); 46 | } 47 | 48 | run() { 49 | this.render(); 50 | } 51 | 52 | createMeshGPU(chunk: VoxelsOnGPU): Model { 53 | const vao = gl.createVertexArray() as WebGLVertexArrayObject; 54 | const position = new ArrayBufferNative(chunk.v, 4 * chunk.index.v * 2, 3, gl.FLOAT); 55 | const positionAttribute = this.shader.getAttributeLocation("position"); 56 | const normalAttribute = this.shader.getAttributeLocation("normal"); 57 | const colorAttribute = this.shader.getAttributeLocation("color"); 58 | const matrix = mat4.create(); 59 | 60 | gl.bindVertexArray(vao); 61 | gl.bindBuffer(gl.ARRAY_BUFFER, position.buffer); 62 | 63 | gl.enableVertexAttribArray(positionAttribute); 64 | gl.vertexAttribPointer(positionAttribute, 3, position.type, position.normalize, 4*3*2 + 4 , 0); 65 | 66 | gl.enableVertexAttribArray(normalAttribute); 67 | gl.vertexAttribPointer(normalAttribute, 3, position.type, position.normalize, 4*3*2 + 4, 4*3); 68 | 69 | gl.enableVertexAttribArray(colorAttribute); 70 | gl.vertexAttribPointer(colorAttribute, 4, gl.UNSIGNED_BYTE, true, 4*3*2 + 4, 4*3*2); 71 | 72 | gl.bindVertexArray(null); 73 | 74 | mat4.fromTranslation(matrix, chunk.id as vec3); 75 | 76 | return { vao, position, matrix, vertexCount: chunk.index.v }; 77 | } 78 | 79 | upload() { 80 | if (this.uploadQueue[0]) { 81 | const chunk = this.uploadQueue.shift(); 82 | const chunkID = map3D1D(chunk.id); 83 | 84 | if (!this.models[chunkID] && chunk.index && chunk.index.v) { 85 | this.models[chunkID] = this.createMeshGPU(chunk); 86 | this.chunks.update(new Float32Array(chunk.rt)); 87 | this.chunkRTBlocks = chunk.index.rt; 88 | } else { 89 | if (chunk.index && chunk.index.v) { 90 | this.models[chunkID].position.updateBuffer(chunk.v, 4 * chunk.index.v * 2); 91 | this.chunks.update(new Float32Array(chunk.rt)); 92 | this.colors.update(new Uint8Array(chunk.colors)); 93 | this.chunkRTBlocks = chunk.index.rt; 94 | } 95 | 96 | if (this.models[chunkID]) { 97 | this.models[chunkID].vertexCount = chunk.index.v; 98 | this.chunkRTBlocks = chunk.index.rt; 99 | } 100 | } 101 | this.grid.meshUploaded(chunkID) 102 | } 103 | } 104 | 105 | render() { 106 | this.upload(); 107 | this.frameBuffer.bind(); 108 | 109 | gl.enable(gl.DEPTH_TEST); 110 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 111 | 112 | gl.useProgram(this.shader.program); 113 | gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); 114 | 115 | let mvp = mat4.create(); 116 | let model: Model; 117 | 118 | mat4.mul(mvp, this.camera.view, mat4.create()); 119 | mat4.mul(mvp, this.camera.perspective, mvp); 120 | 121 | this.oldMVP = this.currentMVP; 122 | 123 | this.currentMVP = mat4.clone(mvp); 124 | 125 | for (const key in this.models) { 126 | model = this.models[key]; 127 | 128 | if (model.vertexCount) { 129 | mat4.identity(mvp); 130 | mat4.mul(mvp, this.camera.view, model.matrix); 131 | mat4.mul(mvp, this.camera.perspective, mvp); 132 | gl.uniformMatrix4fv(this.shader.getUniformLocation("mvp"), false, mvp); 133 | gl.uniform3fv(this.shader.getUniformLocation("offset"), [model.matrix[12], model.matrix[13], model.matrix[14]]) 134 | 135 | gl.bindVertexArray(model.vao); 136 | gl.drawArrays(gl.TRIANGLES, 0, model.vertexCount); 137 | } 138 | } 139 | 140 | this.frameBuffer.flip(); 141 | } 142 | } -------------------------------------------------------------------------------- /src/render/pipeline/v1/node/chunk-node/chunk-node.vs.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | 4 | 5 | uniform mat4 mvp; 6 | uniform vec3 offset; 7 | 8 | 9 | in vec4 position; 10 | in vec3 normal; 11 | in vec4 color; 12 | 13 | out vec4 v_color; 14 | out vec3 v_normal; 15 | out vec4 v_position; 16 | out vec3 wire; 17 | 18 | void main() { 19 | //gl_PointSize = 1.0; 20 | gl_Position = mvp * position; 21 | 22 | v_color = color; 23 | 24 | v_normal = normal; 25 | v_position.xyz = position.xyz + offset; 26 | v_position.w = gl_Position.w; 27 | 28 | 29 | int id = gl_VertexID % 3; 30 | 31 | switch (id) { 32 | case 0: wire = vec3(1.0, 0.0, 0.0); break; 33 | case 1: wire = vec3(0.0, 1.0, 0.0); break; 34 | case 2: wire = vec3(0.0, 0.0, 1.0); break; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/render/pipeline/v1/node/rt-light/rt-light.fs.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision lowp float; 3 | precision lowp int; 4 | 5 | 6 | uniform sampler2D tDiffuse; 7 | uniform sampler2D tNormal; 8 | uniform sampler2D tPosition; 9 | uniform sampler2D tChunks; 10 | uniform sampler2D tColors; 11 | uniform sampler2D tLastRT; 12 | 13 | uniform vec3 cameraPosition; 14 | uniform vec3 cameraRotation; 15 | uniform int rtBlocks; 16 | uniform float frame; 17 | uniform vec2 sampleSize; 18 | uniform mat4 oldMVP; 19 | 20 | in vec2 v_texCoord; 21 | in vec3 rayDirection; 22 | in vec3 rayOrigin; 23 | 24 | layout(location = 0) out vec4 f_color; 25 | 26 | #define MAX_DIST 1e10 27 | #define SUN normalize(vec3(cos(frame * 0.001), sin(frame * 0.001), sin(frame * 0.001))) 28 | #define BOUNCES 3; 29 | 30 | uint baseHash( uvec2 p ) { 31 | p = 1103515245U*((p >> 1U)^(p.yx)); 32 | uint h32 = 1103515245U*((p.x)^(p.y>>3U)); 33 | return h32^(h32 >> 16); 34 | } 35 | 36 | vec3 hash32(uvec2 x) 37 | { 38 | uint n = baseHash(x); 39 | uvec3 rz = uvec3(n, n*16807U, n*48271U); 40 | return vec3((rz >> 1) & uvec3(0x7fffffffU))/float(0x7fffffff); 41 | } 42 | 43 | 44 | vec2 hash2(float seed) { 45 | uint n = baseHash(floatBitsToUint(vec2(seed+=.1,seed+=.1))); 46 | uvec2 rz = uvec2(n, n*48271U); 47 | return vec2(rz.xy & uvec2(0x7fffffffU))/float(0x7fffffff); 48 | } 49 | 50 | vec3 cosWeightedRandomHemisphereDirection( const vec3 n, inout float seed ) { 51 | vec2 r = hash2(seed); 52 | vec3 uu = normalize(cross(n, abs(n.y) > .5 ? vec3(1.,0.,0.) : vec3(0.,1.,0.))); 53 | vec3 vv = cross(uu, n); 54 | float ra = sqrt(r.y); 55 | float rx = ra*cos(6.28318530718*r.x); 56 | float ry = ra*sin(6.28318530718*r.x); 57 | float rz = sqrt(1.-r.y); 58 | vec3 rr = vec3(rx*uu + ry*vv + rz*n); 59 | seed = rr.x; 60 | return normalize(rr); 61 | } 62 | 63 | float iBox( in vec3 ro, in vec3 rd, in vec2 distBound, inout vec3 normal, in vec3 boxSize ) { 64 | vec3 m = sign(rd)/max(abs(rd), 1e-8); 65 | vec3 n = m*ro; 66 | vec3 k = abs(m)*boxSize; 67 | 68 | vec3 t1 = -n - k; 69 | vec3 t2 = -n + k; 70 | 71 | float tN = max( max( t1.x, t1.y ), t1.z ); 72 | float tF = min( min( t2.x, t2.y ), t2.z ); 73 | 74 | if (tN > tF || tF <= 0.) { 75 | return MAX_DIST; 76 | } else { 77 | if (tN >= distBound.x && tN <= distBound.y) { 78 | normal = -sign(rd)*step(t1.yzx,t1.xyz)*step(t1.zxy,t1.xyz); 79 | return tN; 80 | } else if (tF >= distBound.x && tF <= distBound.y) { 81 | normal = -sign(rd)*step(t1.yzx,t1.xyz)*step(t1.zxy,t1.xyz); 82 | return tF; 83 | } else { 84 | return MAX_DIST; 85 | } 86 | } 87 | } 88 | 89 | vec3 sky(vec3 rd) { 90 | float sun_amount = max(dot(rd, SUN), 0.0); 91 | vec3 sun_color = vec3(0.6 , .4, 0.2); 92 | 93 | vec3 sky = mix(vec3(.75, .73, .71), vec3(.5, .7, .9) , 0.25 + rd.z); 94 | sky = sky + sun_color * min(pow(sun_amount, 1500.0) * 5.0, 1.0); 95 | sky = sky + sun_color * min(pow(sun_amount, 10.0) * .6, 1.0); 96 | 97 | return clamp(sky + vec3(SUN.z - 1.0) * vec3(0.5 , .75, 1.0), 0.0, 10.0); 98 | //return sky; 99 | } 100 | 101 | vec4 hit(in vec3 ro, in vec3 rd, inout vec3 normal) { 102 | float rtMin = MAX_DIST; 103 | vec4 blockMin = vec4(1); 104 | vec3 normalMin = vec3(0); 105 | float rt; 106 | vec4 block; 107 | vec2 dist = vec2(.0001, 10); 108 | int minIndex = -1; 109 | 110 | for (int x = 0; x < rtBlocks; ++x) { 111 | block = texelFetch(tChunks, ivec2(x, 0), 0); 112 | if (block.w == 0.0) { 113 | break; 114 | } 115 | rt = iBox(ro - block.xyz, rd, dist, normal, vec3(block.w)); 116 | if (rt < rtMin) { 117 | rtMin = rt; 118 | normalMin = normal; 119 | blockMin = block; 120 | minIndex = x; 121 | } 122 | } 123 | 124 | normal = normalMin; 125 | 126 | 127 | vec4 blockColor = minIndex != -1 ? texelFetch(tColors, ivec2(minIndex, 0), 0) : vec4(0); 128 | return vec4(minIndex, 0, 0, rtMin); 129 | } 130 | 131 | 132 | 133 | void main() { 134 | 135 | vec4 d = texture(tDiffuse, v_texCoord); 136 | vec4 n = texture(tNormal, v_texCoord); 137 | vec4 p = texture(tPosition, v_texCoord); 138 | vec4 l = texture(tLastRT, v_texCoord); 139 | 140 | 141 | 142 | if (length(n.xyz) < 0.1 || length(n.xyz) > 1.1) { 143 | f_color.xyz = sky(normalize(rayDirection)); 144 | } else { 145 | vec4 block; 146 | vec3 albedo = d.rgb; 147 | vec2 dist = vec2(.0001, 10); 148 | 149 | vec3 rand = hash32(uvec2(v_texCoord * 4096.0 * frame)) * 2.0 - 1.0; 150 | vec3 normal = n.xyz; 151 | vec4 result = vec4(0); 152 | vec3 ro = p.xyz; 153 | vec3 rd = vec3(0); 154 | float seed = (v_texCoord.x + v_texCoord.y * frame) * 1.0; 155 | 156 | 157 | rd = cosWeightedRandomHemisphereDirection(normal, seed); 158 | //rd = normalize(rand + normal); 159 | 160 | ro += rd * result.w; 161 | int len = int(gl_FragCoord.x * gl_FragCoord.y + frame) % 3 + 1; 162 | 163 | int i = 0; 164 | 165 | if (d.w == 0.0) { 166 | while (true) { 167 | 168 | 169 | result = hit(ro , rd, normal); 170 | vec4 block = texelFetch(tColors, ivec2(result.x, 0), 0); 171 | 172 | 173 | if (result.w > 1.0) { 174 | albedo *= sky(rd); 175 | break; 176 | } else if (block.w > 0.0){ 177 | albedo *= block.rgb + block.rgb * block.w * 1.25; 178 | break; 179 | } else { 180 | if (len == ++i) { 181 | 182 | // last try to hit sun 183 | rd = normalize(rand + SUN * 64.0); 184 | result = hit(ro, rd, normal); 185 | 186 | if (result.w > 1.0) { 187 | albedo *= block.rgb * sky(rd); 188 | } else { 189 | albedo *= 0.0; 190 | } 191 | 192 | break; 193 | } 194 | 195 | 196 | albedo *= block.rgb; 197 | 198 | ro += rd * result.w; 199 | rd = cosWeightedRandomHemisphereDirection(normal, seed); 200 | 201 | } 202 | } 203 | 204 | } else { 205 | albedo += albedo; 206 | } 207 | 208 | 209 | f_color.rgb = (albedo ) ; 210 | //f_color.w = p.w; 211 | 212 | 213 | } 214 | } -------------------------------------------------------------------------------- /src/render/pipeline/v1/node/rt-light/rt-light.ts: -------------------------------------------------------------------------------- 1 | import {FrameBuffer, SimpleNode} from "@foxel_fox/glib" 2 | import {Shader} from "@foxel_fox/glib"; 3 | import {Quad} from "@foxel_fox/glib"; 4 | import {canvas, gl} from "../../../../context"; 5 | import {Texture} from "@foxel_fox/glib"; 6 | import {Camera} from "../../../../camera"; 7 | import {mat4} from "gl-matrix"; 8 | import {ChunkNode} from "../chunk-node/chunk-node"; 9 | 10 | export class RTLightNode extends SimpleNode { 11 | 12 | size = undefined; 13 | frame = 1; 14 | 15 | constructor( 16 | private diffuse: Texture, 17 | private normal: Texture, 18 | private position: Texture, 19 | private camera: Camera, 20 | private chunks: Texture, 21 | private colors: Texture, 22 | private chunkNode: ChunkNode, 23 | ) { 24 | super(new Shader(require("./rt-light.vs.glsl"), require("./rt-light.fs.glsl")), new Quad() as {}); 25 | } 26 | 27 | init() { 28 | 29 | const output = new Texture(undefined, undefined, null, gl.RGBA32F, gl.RGBA, gl.FLOAT); 30 | 31 | 32 | this.frameBuffer = new FrameBuffer([output], true, false); 33 | 34 | } 35 | 36 | run() { 37 | this.frameBuffer.bind(); 38 | gl.viewport(0, 0, canvas.width, canvas.height); 39 | //gl.viewport(0, 0, this.size, this.size); 40 | 41 | gl.useProgram(this.shader.program); 42 | 43 | gl.activeTexture(gl.TEXTURE0); 44 | gl.uniform1i(this.shader.getUniformLocation("tDiffuse"), 0); 45 | gl.bindTexture(gl.TEXTURE_2D, this.diffuse.webGLTexture); 46 | 47 | 48 | gl.activeTexture(gl.TEXTURE1); 49 | gl.uniform1i(this.shader.getUniformLocation("tNormal"), 1); 50 | gl.bindTexture(gl.TEXTURE_2D, this.normal.webGLTexture); 51 | 52 | gl.activeTexture(gl.TEXTURE2); 53 | gl.uniform1i(this.shader.getUniformLocation("tPosition"), 2); 54 | gl.bindTexture(gl.TEXTURE_2D, this.position.webGLTexture); 55 | 56 | gl.activeTexture(gl.TEXTURE3); 57 | gl.uniform1i(this.shader.getUniformLocation("tChunks"), 3); 58 | gl.bindTexture(gl.TEXTURE_2D, this.chunks.webGLTexture); 59 | 60 | gl.activeTexture(gl.TEXTURE4); 61 | gl.uniform1i(this.shader.getUniformLocation("tLastRT"), 4); 62 | gl.bindTexture(gl.TEXTURE_2D, this.frameBuffer.textures[0].webGLTexture); 63 | 64 | gl.activeTexture(gl.TEXTURE5); 65 | gl.uniform1i(this.shader.getUniformLocation("tColors"), 5); 66 | gl.bindTexture(gl.TEXTURE_2D, this.colors.webGLTexture); 67 | 68 | gl.uniform3fv(this.shader.getUniformLocation("cameraPosition"), this.camera.position); 69 | gl.uniform3fv(this.shader.getUniformLocation("cameraRotation"), [this.camera.rotX, this.camera.rotY, 0 ]); 70 | 71 | gl.uniform1i(this.shader.getUniformLocation("rtBlocks"), this.chunkNode.chunkRTBlocks); 72 | gl.uniform2f(this.shader.getUniformLocation("sampleSize"), 1 / canvas.width, 1 / canvas.height); 73 | gl.uniform1f(this.shader.getUniformLocation("frame"), this.frame); 74 | gl.uniformMatrix4fv(this.shader.getUniformLocation("oldMVP"), false, this.chunkNode.oldMVP); 75 | 76 | if (++this.frame > 1000000) { 77 | this.frame = 1; 78 | } 79 | 80 | 81 | let mvp = mat4.create(); 82 | let modelMatrix = mat4.create(); 83 | 84 | mat4.identity(mvp); 85 | mat4.mul(mvp, this.camera.view, modelMatrix); 86 | mat4.mul(mvp, this.camera.perspective, mvp); 87 | mat4.invert(mvp, mvp); 88 | gl.uniformMatrix4fv(this.shader.getUniformLocation("inverseMVP"), false, mvp); 89 | 90 | 91 | 92 | gl.bindVertexArray(this.vao); 93 | gl.drawArrays(gl.TRIANGLES, 0, 6); 94 | 95 | this.frameBuffer.flip(); 96 | } 97 | } -------------------------------------------------------------------------------- /src/render/pipeline/v1/node/rt-light/rt-light.vs.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | in vec4 position; 4 | in vec2 a_texCoord; 5 | 6 | out vec3 rayDirection; 7 | out vec3 rayOrigin; 8 | out vec2 v_texCoord; 9 | 10 | 11 | uniform mat4 inverseMVP; 12 | 13 | void main() { 14 | gl_Position = position; 15 | v_texCoord = a_texCoord; 16 | 17 | 18 | // Calculate the farplane vertex position. 19 | // Input is clipspace, output is worldspace 20 | vec4 farPlane = inverseMVP * vec4(position.xy, 1.0, 1.0); 21 | farPlane /= farPlane.w; 22 | 23 | // Same as above for the near plane. 24 | // Remember the near plane in OpenGL is at z=-1 25 | vec4 nearPlane = inverseMVP * vec4(position.xy, -1.0, 1.0); 26 | nearPlane /= nearPlane.w; 27 | 28 | // No need to normalize ray direction here, as it might get 29 | // interpolated (and become non-unit) before going to the fragment 30 | // shader. 31 | rayDirection = normalize(farPlane.xyz - nearPlane.xyz); 32 | rayOrigin = -nearPlane.xyz; 33 | } -------------------------------------------------------------------------------- /src/render/pipeline/v1/pipeline-v1.ts: -------------------------------------------------------------------------------- 1 | import {RTLightNode} from "./node/rt-light/rt-light"; 2 | import {Camera} from "../../camera"; 3 | import {OctreeGrid} from "../../../octree/grid"; 4 | import {EditNode} from "../shared-node/edit/edit-node"; 5 | import {ChunkNode} from "./node/chunk-node/chunk-node"; 6 | import {Denoiser} from "../shared-node/denoiser/denoiser"; 7 | import {OutputNode} from "../shared-node/output/output"; 8 | import {generateStartScene} from "../../../generator/start-scene"; 9 | 10 | export class PipelineV1 { 11 | 12 | chunkNode: ChunkNode; 13 | rtLightNode: RTLightNode; 14 | denoiser: Denoiser; 15 | edit: EditNode; 16 | output: OutputNode; 17 | 18 | constructor ( 19 | public grid: OctreeGrid, 20 | public camera: Camera 21 | ) { 22 | 23 | this.chunkNode = new ChunkNode(this.camera, grid); 24 | this.chunkNode.init(); 25 | 26 | this.edit = new EditNode(undefined, this.camera, grid); 27 | this.edit.init(); 28 | 29 | this.rtLightNode = new RTLightNode( 30 | this.chunkNode.frameBuffer.textures[0], 31 | this.chunkNode.frameBuffer.textures[1], 32 | this.chunkNode.frameBuffer.textures[2], 33 | this.camera, 34 | this.chunkNode.chunks, 35 | this.chunkNode.colors, 36 | this.chunkNode 37 | ); 38 | this.rtLightNode.init(); 39 | 40 | this.denoiser = new Denoiser( 41 | this.chunkNode, 42 | this.chunkNode.frameBuffer.textures[0], 43 | this.chunkNode.frameBuffer.textures[1], 44 | this.chunkNode.frameBuffer.textures[2], 45 | this.rtLightNode.frameBuffer.textures[0], 46 | this.camera 47 | ); 48 | this.denoiser.init(); 49 | 50 | this.output = new OutputNode(this.denoiser); 51 | this.output.init(); 52 | } 53 | 54 | run() { 55 | this.camera.update(); 56 | this.chunkNode.run(); 57 | this.edit.run(); 58 | this.rtLightNode.run(); 59 | this.denoiser.run(); 60 | this.output.run(); 61 | } 62 | } -------------------------------------------------------------------------------- /src/render/pipeline/v2/node/rt-chunk-node/rt-chunk-node.fs.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | uniform sampler2D chunks; 5 | uniform mat4 mvp; 6 | uniform vec3 cameraPosition; 7 | uniform vec3 cameraRotation; 8 | uniform vec2 iResolution; 9 | uniform float iTime; 10 | uniform vec2 iMouse; 11 | uniform int iFrame; 12 | 13 | in vec2 v_texCoord; 14 | in vec3 rayDirection; 15 | in vec3 rayOrigin; 16 | 17 | #define MAX_DIST 1e10 18 | #define PATH_LENGTH 12 19 | #define LAMBERTIAN 0. 20 | #define METAL 1. 21 | #define DIELECTRIC 2. 22 | 23 | layout(location = 0) out vec4 f_color; 24 | layout(location = 1) out vec3 f_normal; 25 | layout(location = 2) out vec4 f_position; 26 | layout(location = 3) out vec4 f_albedo; 27 | 28 | 29 | const float EPS = 0.0001; 30 | const float START_T = 100000.0; 31 | const int MAX_DEPTH = 3; 32 | 33 | vec3 opU( vec3 d, float iResult, float mat ) { 34 | return (iResult < d.y) ? vec3(d.x, iResult, mat) : d; 35 | } 36 | 37 | uint baseHash( uvec2 p ) { 38 | p = 1103515245U*((p >> 1U)^(p.yx)); 39 | uint h32 = 1103515245U*((p.x)^(p.y>>3U)); 40 | return h32^(h32 >> 16); 41 | } 42 | 43 | float hash1( inout float seed ) { 44 | uint n = baseHash(floatBitsToUint(vec2(seed+=.1,seed+=.1))); 45 | return float(n)/float(0xffffffffU); 46 | } 47 | 48 | vec2 hash2( inout float seed ) { 49 | uint n = baseHash(floatBitsToUint(vec2(seed+=.1,seed+=.1))); 50 | uvec2 rz = uvec2(n, n*48271U); 51 | return vec2(rz.xy & uvec2(0x7fffffffU))/float(0x7fffffff); 52 | } 53 | 54 | float FresnelSchlickRoughness( float cosTheta, float F0, float roughness ) { 55 | return F0 + (max((1. - roughness), F0) - F0) * pow(abs(1. - cosTheta), 5.0); 56 | } 57 | 58 | vec3 cosWeightedRandomHemisphereDirection( const vec3 n, inout float seed ) { 59 | vec2 r = hash2(seed); 60 | vec3 uu = normalize(cross(n, abs(n.y) > .5 ? vec3(1.,0.,0.) : vec3(0.,1.,0.))); 61 | vec3 vv = cross(uu, n); 62 | float ra = sqrt(r.y); 63 | float rx = ra*cos(6.28318530718*r.x); 64 | float ry = ra*sin(6.28318530718*r.x); 65 | float rz = sqrt(1.-r.y); 66 | vec3 rr = vec3(rx*uu + ry*vv + rz*n); 67 | return normalize(rr); 68 | } 69 | 70 | bool modifiedRefract(const in vec3 v, const in vec3 n, const in float ni_over_nt, 71 | out vec3 refracted) { 72 | float dt = dot(v, n); 73 | float discriminant = 1. - ni_over_nt*ni_over_nt*(1.-dt*dt); 74 | if (discriminant > 0.) { 75 | refracted = ni_over_nt*(v - n*dt) - n*sqrt(discriminant); 76 | return true; 77 | } else { 78 | return false; 79 | } 80 | } 81 | 82 | vec3 modifyDirectionWithRoughness( const vec3 normal, const vec3 n, const float roughness, inout float seed ) { 83 | vec2 r = hash2(seed); 84 | 85 | vec3 uu = normalize(cross(n, abs(n.y) > .5 ? vec3(1.,0.,0.) : vec3(0.,1.,0.))); 86 | vec3 vv = cross(uu, n); 87 | 88 | float a = roughness*roughness; 89 | 90 | float rz = sqrt(abs((1.0-r.y) / clamp(1.+(a - 1.)*r.y,.00001,1.))); 91 | float ra = sqrt(abs(1.-rz*rz)); 92 | float rx = ra*cos(6.28318530718*r.x); 93 | float ry = ra*sin(6.28318530718*r.x); 94 | vec3 rr = vec3(rx*uu + ry*vv + rz*n); 95 | 96 | vec3 ret = normalize(rr); 97 | return dot(ret,normal) > 0. ? ret : n; 98 | } 99 | 100 | vec2 randomInUnitDisk( inout float seed ) { 101 | vec2 h = hash2(seed) * vec2(1,6.28318530718); 102 | float phi = h.y; 103 | float r = sqrt(h.x); 104 | return r*vec2(sin(phi),cos(phi)); 105 | } 106 | 107 | float iBox( in vec3 ro, in vec3 rd, in vec2 distBound, inout vec3 normal, in vec3 boxSize ) { 108 | vec3 m = sign(rd)/max(abs(rd), 1e-8); 109 | vec3 n = m*ro; 110 | vec3 k = abs(m)*boxSize; 111 | 112 | vec3 t1 = -n - k; 113 | vec3 t2 = -n + k; 114 | 115 | float tN = max( max( t1.x, t1.y ), t1.z ); 116 | float tF = min( min( t2.x, t2.y ), t2.z ); 117 | 118 | if (tN > tF || tF <= 0.) { 119 | return MAX_DIST; 120 | } else { 121 | if (tN >= distBound.x && tN <= distBound.y) { 122 | normal = -sign(rd)*step(t1.yzx,t1.xyz)*step(t1.zxy,t1.xyz); 123 | return tN; 124 | } else if (tF >= distBound.x && tF <= distBound.y) { 125 | normal = -sign(rd)*step(t1.yzx,t1.xyz)*step(t1.zxy,t1.xyz); 126 | return tF; 127 | } else { 128 | return MAX_DIST; 129 | } 130 | } 131 | } 132 | 133 | 134 | vec3 worldhit( in vec3 ro, in vec3 rd, in vec2 dist, out vec3 normal ) { 135 | vec3 tmp0, tmp1, d = vec3(dist, 0.); 136 | 137 | vec4 block; 138 | for (int x = 0; x < 1024; ++x) { 139 | block = texelFetch(chunks, ivec2(x, 0), 0); 140 | if (block.w == 0.0) { 141 | break; 142 | } 143 | d = opU(d, iBox(ro - block.xyz, rd, d.xy, normal, vec3(block.w)), 7.); 144 | } 145 | 146 | return d; 147 | } 148 | 149 | vec3 pal(in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d) { 150 | return a + b*cos(6.28318530718*(c*t+d)); 151 | } 152 | 153 | float checkerBoard( vec2 p ) { 154 | return mod(floor(p.x) + floor(p.y), 2.); 155 | } 156 | 157 | vec3 getSkyColor( vec3 rd ) { 158 | // vec3 col = mix(vec3(1),vec3(.5,.7,1), .5+.5*rd.y); 159 | // float sun = clamp(dot(normalize(vec3(-.4,.7,-.6)),rd), 0., 1.); 160 | // col += vec3(1,.6,.1)*(pow(sun,4.) + 10.*pow(sun,32.)); 161 | return vec3(max(dot(rd, vec3(0,0,1)), 0.25)); 162 | } 163 | 164 | 165 | 166 | float gpuIndepentHash(float p) { 167 | p = fract(p * .1031); 168 | p *= p + 19.19; 169 | p *= p + p; 170 | return fract(p); 171 | } 172 | 173 | void getMaterialProperties(in vec3 pos, in float mat, 174 | out vec3 albedo, out float type, out float roughness) { 175 | albedo = pal(mat*.59996323+.5, vec3(.5),vec3(.5),vec3(1),vec3(0,.1,.2)); 176 | 177 | if( mat < 1.5 ) { 178 | albedo = vec3(.25 + .25*checkerBoard(pos.xz * 5.)); 179 | roughness = .75 * albedo.x - .15; 180 | type = METAL; 181 | } else { 182 | type = floor(gpuIndepentHash(mat+.3) * 3.); 183 | roughness = (1.-type*.475) * gpuIndepentHash(mat); 184 | } 185 | } 186 | 187 | float schlick(float cosine, float r0) { 188 | return r0 + (1.-r0)*pow((1.-cosine),5.); 189 | } 190 | vec3 render( in vec3 ro, in vec3 rd, inout float seed ) { 191 | vec3 albedo, normal, col = vec3(1.); 192 | float roughness, type; 193 | 194 | for (int i=0; i 0.) { 197 | ro += rd * res.y; 198 | 199 | getMaterialProperties(ro, res.z, albedo, type, roughness); 200 | 201 | if (type < LAMBERTIAN+.5) { // Added/hacked a reflection term 202 | float F = FresnelSchlickRoughness(max(0.,-dot(normal, rd)), .04, roughness); 203 | if (F > hash1(seed)) { 204 | rd = modifyDirectionWithRoughness(normal, reflect(rd,normal), roughness, seed); 205 | } else { 206 | col *= albedo; 207 | rd = cosWeightedRandomHemisphereDirection(normal, seed); 208 | } 209 | } else if (type < METAL+.5) { 210 | col *= albedo; 211 | rd = modifyDirectionWithRoughness(normal, reflect(rd,normal), roughness, seed); 212 | } else { // DIELECTRIC 213 | vec3 normalOut, refracted; 214 | float ni_over_nt, cosine, reflectProb = 1.; 215 | if (dot(rd, normal) > 0.) { 216 | normalOut = -normal; 217 | ni_over_nt = 1.4; 218 | cosine = dot(rd, normal); 219 | cosine = sqrt(1.-(1.4*1.4)-(1.4*1.4)*cosine*cosine); 220 | } else { 221 | normalOut = normal; 222 | ni_over_nt = 1./1.4; 223 | cosine = -dot(rd, normal); 224 | } 225 | 226 | if (modifiedRefract(rd, normalOut, ni_over_nt, refracted)) { 227 | float r0 = (1.-ni_over_nt)/(1.+ni_over_nt); 228 | reflectProb = FresnelSchlickRoughness(cosine, r0*r0, roughness); 229 | } 230 | 231 | rd = hash1(seed) <= reflectProb ? reflect(rd,normal) : refracted; 232 | rd = modifyDirectionWithRoughness(-normalOut, rd, roughness, seed); 233 | } 234 | if (i == 0) { 235 | f_albedo.rgb = col; 236 | f_normal.rgb = normal; 237 | f_position.rgb = ro; 238 | } 239 | } else { 240 | col *= getSkyColor(rd); 241 | return col; 242 | } 243 | } 244 | return vec3(0); 245 | } 246 | 247 | 248 | 249 | void main() 250 | { 251 | 252 | 253 | //vec4 data = vec4(0.0); 254 | 255 | 256 | 257 | //vec3 normal; 258 | 259 | //float fpd = data.x; 260 | 261 | //vec2 p = (-iResolution.xy + 2.* vec2(gl_FragCoord) - 1.)/iResolution.y; 262 | // float seed = float(baseHash(floatBitsToUint(p - iTime)))/float(0xffffffffU); 263 | float seed = 1.0; 264 | // AA 265 | // p += 2.*hash2(seed)/iResolution.y; 266 | // vec3 rd = ca * normalize( vec3(p.xy,1.6) ); 267 | 268 | // DOF 269 | // vec3 fp = ro + rd * fpd; 270 | // ro = ro + ca * vec3(randomInUnitDisk(seed), 0.)*.02; 271 | // rd = normalize(fp - ro); 272 | 273 | vec3 col = render(rayOrigin, rayDirection, seed); 274 | 275 | f_albedo.rgb = vec3(1.0, 0.0, 0.0); 276 | f_normal.rgb = vec3(0.0, 1.0, 0.0); 277 | f_position.rgb = vec3(0.0, 0.0, 1.0); 278 | f_color = vec4(col, 1.0); 279 | 280 | 281 | } -------------------------------------------------------------------------------- /src/render/pipeline/v2/node/rt-chunk-node/rt-chunk-node.ts: -------------------------------------------------------------------------------- 1 | import {Shader, SimpleNode, ArrayBufferNative, FrameBuffer, Texture, Quad} from "@foxel_fox/glib"; 2 | import {canvas, gl} from "../../../../context"; 3 | import {mat4} from "gl-matrix"; 4 | import {Camera} from "../../../../camera"; 5 | import {OctreeGrid} from "../../../../../octree/grid"; 6 | import {map3D1D} from "../../../../../octree/util"; 7 | 8 | interface Model { 9 | vao: WebGLVertexArrayObject 10 | position: ArrayBufferNative 11 | matrix: mat4 12 | vertexCount: number 13 | } 14 | 15 | export class RTChunkNode extends SimpleNode { 16 | 17 | chunks: Texture; 18 | colors: Texture; 19 | uploadQueue = []; 20 | frame = 0; 21 | oldMVP: mat4; 22 | currentMVP = mat4.create(); 23 | 24 | constructor ( 25 | private camera: Camera, 26 | private grid: OctreeGrid 27 | ) { 28 | super(new Shader( 29 | require("./rt-chunk-node.vs.glsl"), 30 | require("./rt-chunk-node.fs.glsl") 31 | ), new Quad() as {}); 32 | 33 | } 34 | 35 | init(): void { 36 | const diffuse = new Texture(undefined, undefined, null, undefined, undefined, undefined, gl.LINEAR); 37 | const normal = new Texture(undefined, undefined, null, gl.RGBA16F, gl.RGBA, gl.FLOAT, gl.LINEAR); 38 | const position = new Texture(undefined, undefined, null, gl.RGBA32F, gl.RGBA, gl.FLOAT); 39 | const albedo = new Texture(undefined, undefined, null, undefined, undefined, undefined, gl.LINEAR); 40 | 41 | this.chunks = new Texture(4096, 1, undefined, gl.RGBA32F, gl.RGBA, gl.FLOAT); 42 | this.colors = new Texture(4096, 1); 43 | 44 | this.frameBuffer = new FrameBuffer([diffuse, normal, position, albedo], true, true); 45 | } 46 | 47 | run() { 48 | this.render(); 49 | } 50 | 51 | upload() { 52 | if (this.uploadQueue[0]) { 53 | const chunk = this.uploadQueue.shift(); 54 | const chunkID = map3D1D(chunk.id); 55 | 56 | this.chunks.update(new Float32Array(chunk.rt)); 57 | 58 | this.grid.meshUploaded(chunkID) 59 | } 60 | } 61 | 62 | render() { 63 | this.upload(); 64 | //this.frameBuffer.bind(); 65 | 66 | gl.enable(gl.DEPTH_TEST); 67 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 68 | 69 | gl.useProgram(this.shader.program); 70 | gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); 71 | 72 | 73 | let mvp = mat4.create(); 74 | let modelMatrix = mat4.create(); 75 | mat4.fromTranslation(modelMatrix, [0 , 0, 0]); 76 | 77 | mat4.identity(mvp); 78 | mat4.mul(mvp, this.camera.view, modelMatrix); 79 | mat4.mul(mvp, this.camera.perspective, mvp); 80 | mat4.invert(mvp, mvp); 81 | 82 | this.oldMVP = this.currentMVP; 83 | this.currentMVP = mat4.clone(mvp); 84 | 85 | gl.uniformMatrix4fv(this.shader.getUniformLocation("inverseMVP"), false, mvp); 86 | gl.uniform3fv(this.shader.getUniformLocation("cameraPosition"), this.camera.position); 87 | gl.uniform3fv(this.shader.getUniformLocation("cameraRotation"), [0, this.camera.rotY, this.camera.rotX ]); 88 | gl.uniform2fv(this.shader.getUniformLocation("iResolution"), [canvas.width, canvas.height]); 89 | gl.uniform1f(this.shader.getUniformLocation("iTime"), Date.now()); 90 | gl.uniform2fv(this.shader.getUniformLocation("iMouse"), [Math.floor(Math.abs(this.camera.rotX)) * 10, Math.floor(Math.abs(this.camera.rotY)) * 10]); 91 | 92 | gl.uniform1ui(this.shader.getUniformLocation("iFrame"), this.frame); 93 | this.frame++; 94 | 95 | 96 | 97 | gl.uniform1i(this.shader.getUniformLocation("chunks"), 0); 98 | gl.activeTexture(gl.TEXTURE0); 99 | gl.bindTexture(gl.TEXTURE_2D, this.chunks.webGLTexture); 100 | 101 | gl.bindVertexArray(this.vao); 102 | gl.drawArrays(gl.TRIANGLES, 0, 6); 103 | 104 | 105 | 106 | } 107 | } -------------------------------------------------------------------------------- /src/render/pipeline/v2/node/rt-chunk-node/rt-chunk-node.vs.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | in vec4 position; 5 | 6 | in vec2 a_texCoord; 7 | out vec2 v_texCoord; 8 | out vec3 rayDirection; 9 | out vec3 rayOrigin; 10 | uniform mat4 inverseMVP; 11 | 12 | void main() { 13 | gl_Position = position; 14 | v_texCoord = a_texCoord; 15 | 16 | 17 | 18 | 19 | // Calculate the farplane vertex position. 20 | // Input is clipspace, output is worldspace 21 | vec4 farPlane = inverseMVP * vec4(position.xy, 1.0, 1.0); 22 | farPlane /= farPlane.w; 23 | 24 | // Same as above for the near plane. 25 | // Remember the near plane in OpenGL is at z=-1 26 | vec4 nearPlane = inverseMVP * vec4(position.xy, -1.0, 1.0); 27 | nearPlane /= nearPlane.w; 28 | 29 | // No need to normalize ray direction here, as it might get 30 | // interpolated (and become non-unit) before going to the fragment 31 | // shader. 32 | rayDirection = normalize(farPlane.xyz - nearPlane.xyz); 33 | rayOrigin = nearPlane.xyz; 34 | 35 | } -------------------------------------------------------------------------------- /src/render/pipeline/v2/pipeline-v2.ts: -------------------------------------------------------------------------------- 1 | import {Camera} from "../../camera"; 2 | import {OctreeGrid} from "../../../octree/grid"; 3 | import {RTChunkNode} from "./node/rt-chunk-node/rt-chunk-node"; 4 | import {EditNode} from "../shared-node/edit/edit-node"; 5 | import {OutputNode} from "../shared-node/output/output"; 6 | import {Denoiser} from "../shared-node/denoiser/denoiser"; 7 | 8 | export class PipelineV2 { 9 | 10 | chunkNode: RTChunkNode; 11 | edit: EditNode; 12 | denoiser: Denoiser; 13 | output: OutputNode; 14 | 15 | constructor( 16 | public grid: OctreeGrid, 17 | public camera: Camera 18 | ) { 19 | this.chunkNode = new RTChunkNode(this.camera, grid); 20 | this.chunkNode.init(); 21 | 22 | this.edit = new EditNode(undefined, this.camera, grid); 23 | this.edit.init(); 24 | 25 | this.denoiser = new Denoiser( 26 | this.chunkNode, 27 | this.chunkNode.frameBuffer.textures[0], 28 | this.chunkNode.frameBuffer.textures[1], 29 | this.chunkNode.frameBuffer.textures[2], 30 | this.chunkNode.frameBuffer.textures[3], 31 | this.camera 32 | ); 33 | this.denoiser.init(); 34 | 35 | this.output = new OutputNode(this.denoiser); 36 | this.output.init(); 37 | } 38 | 39 | run() { 40 | this.camera.update(); 41 | this.chunkNode.run(); 42 | this.edit.run(); 43 | this.denoiser.run(); 44 | this.output.run(); 45 | } 46 | } -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | class headers{ 2 | middleware(config){ 3 | return async (ctx,next)=>{ 4 | ctx.response.set("Cross-Origin-Opener-Policy", "same-origin"); 5 | ctx.response.set("Cross-Origin-Embedder-Policy", "require-corp"); 6 | await next(); 7 | } 8 | } 9 | } 10 | 11 | module.exports = headers; -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "es2015", 5 | "moduleResolution": "node" 6 | } 7 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const ThreadsPlugin = require('threads-plugin'); 3 | 4 | module.exports = { 5 | entry: "./src/index.ts", 6 | resolve: { 7 | extensions: [".ts", ".js", ".json"] 8 | }, 9 | module: { 10 | rules: [{ 11 | test: /\.ts?$/, 12 | loader: "ts-loader", 13 | options: { 14 | compilerOptions: { 15 | module: "esnext" 16 | } 17 | } 18 | }, { 19 | test: /\.glsl$/, 20 | loader: "shader-loader" 21 | }] 22 | }, 23 | plugins: [ 24 | new HtmlWebpackPlugin({ 25 | title: "Voxel Octree", 26 | }), 27 | new ThreadsPlugin({path: "/"}) 28 | ] 29 | }; --------------------------------------------------------------------------------