├── .gitignore ├── src ├── assets │ ├── roots2_rgbd.png │ └── ruins_rgbd.png ├── style.css └── index.js ├── package.json ├── index.html └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .parcel-cache -------------------------------------------------------------------------------- /src/assets/roots2_rgbd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thygate/depthmap-viewer-three/HEAD/src/assets/roots2_rgbd.png -------------------------------------------------------------------------------- /src/assets/ruins_rgbd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thygate/depthmap-viewer-three/HEAD/src/assets/ruins_rgbd.png -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | margin: 0; 4 | } 5 | .dropzone { 6 | box-sizing: border-box; 7 | display: none; 8 | position: fixed; 9 | width: 100%; 10 | height: 100%; 11 | left: 0; 12 | top: 0; 13 | z-index: 99999; 14 | background: rgba(#60a7dc,.8); 15 | border: 11px dashed #60a7dc; 16 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "depthmap-viewer-three", 3 | "version": "1.0.0", 4 | "description": "", 5 | "default": "index.html", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "serve ." 9 | }, 10 | "author": "thygate", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "serve": "^14.2.4" 14 | }, 15 | "dependencies": { 16 | "three": "^0.146.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | depthmap-viewer-three 6 | 7 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # depthmap-viewer-three 2 | 3 | Simple web-based interactive depthmap viewer. Using [threejs](https://threejs.org/) to render a plane with a displacement map. 4 | 5 | `Drag-and-drop` images with combined-rgb-and-depth-horizontally into the window to view them. 6 | 7 | LIVE at https://thygate.github.io/depthmap-viewer-three 8 | 9 | ## Example input image 10 | 11 | ![example](src/assets/roots2_rgbd.png) 12 | >Image was generated with stable diffusion using [AUTOMATIC1111's Stable Diffusion Web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) and [stable-diffusion-webui-depthmap-script](https://github.com/thygate/stable-diffusion-webui-depthmap-script). 13 | 14 | ## Running 15 | 16 | The following installs dev and non-dev dependencies, including `three` for 17 | rendering, and `serve` which is used to serve the website locally: 18 | 19 | ``` 20 | npm clean-install 21 | npm start 22 | ``` 23 | 24 | To install for production without dev dependencies (namely, without `serve`) 25 | run the following then host the files anywhere you want: 26 | 27 | ``` 28 | npm clean-install --production 29 | ``` -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; 3 | import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'; 4 | 5 | const MyTexture = new URL("./assets/roots2_rgbd.png", import.meta.url); 6 | 7 | let mesh; 8 | let material; 9 | let image_ar; 10 | 11 | const settings = { 12 | metalness: 0.0, 13 | roughness: 0.14, 14 | ambientIntensity: 0.85, 15 | displacementScale: 5, 16 | displacementBias: -0.5, 17 | }; 18 | 19 | // init 20 | const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000); 21 | camera.position.z = 3; 22 | 23 | const scene = new THREE.Scene(); 24 | 25 | const ambientLight = new THREE.AmbientLight( 0xffffff, 0.5 ); 26 | scene.add( ambientLight ); 27 | 28 | const pointLight = new THREE.PointLight( 0xff0000, 0.5 ); 29 | pointLight.position.z = 2500; 30 | scene.add( pointLight ); 31 | 32 | 33 | const renderer = new THREE.WebGLRenderer( { antialias: true } ); 34 | renderer.setSize( window.innerWidth, window.innerHeight ); 35 | renderer.setAnimationLoop( animation ); 36 | //renderer.xr.enabled = true; 37 | renderer.toneMapping = THREE.ACESFilmicToneMapping; 38 | renderer.toneMappingExposure = 1; 39 | renderer.outputEncoding = THREE.sRGBEncoding; 40 | document.body.appendChild( renderer.domElement ); 41 | 42 | // animation 43 | function animation( time ) { 44 | 45 | //mesh.rotation.x = time / 2000; 46 | //mesh.rotation.y = time / 1000; 47 | 48 | renderer.render( scene, camera ); 49 | 50 | } 51 | 52 | function onWindowResize() { 53 | 54 | const aspect = window.innerWidth / window.innerHeight; 55 | camera.aspect = aspect; 56 | camera.updateProjectionMatrix(); 57 | 58 | renderer.setSize( window.innerWidth, window.innerHeight ); 59 | 60 | } 61 | window.addEventListener( 'resize', onWindowResize ); 62 | 63 | 64 | // orbit controls 65 | const controls = new OrbitControls( camera, renderer.domElement ); 66 | controls.enableZoom = true; 67 | controls.enableDamping = true; 68 | 69 | 70 | const image = new Image(); 71 | image.onload = function() { 72 | 73 | if (mesh) { 74 | mesh.geometry.dispose(); 75 | mesh.material.dispose(); 76 | scene.remove( mesh ); 77 | } 78 | 79 | image_ar = image.width / image.height / 2; 80 | 81 | const ctx = document.createElement('canvas').getContext('2d'); 82 | ctx.canvas.width = image.width / 2; 83 | ctx.canvas.height = image.height; 84 | ctx.drawImage(image, 0, 0, image.width / 2, image.height, 0, 0, image.width / 2, image.height); 85 | const myrgbmap = new THREE.CanvasTexture(ctx.canvas); 86 | 87 | const ctx2 = document.createElement('canvas').getContext('2d'); 88 | ctx2.canvas.width = image.width / 2; 89 | ctx2.canvas.height = image.height; 90 | ctx2.drawImage(image, image.width / 2, 0, image.width / 2, image.height, 0, 0, image.width / 2, image.height); 91 | const mydepthmap = new THREE.CanvasTexture(ctx2.canvas); 92 | 93 | 94 | // material 95 | material = new THREE.MeshStandardMaterial( { 96 | 97 | color: 0xaaaaaa, 98 | roughness: settings.roughness, 99 | metalness: settings.metalness, 100 | 101 | map: myrgbmap, 102 | 103 | displacementMap: mydepthmap, 104 | displacementScale: settings.displacementScale, 105 | displacementBias: settings.displacementBias, 106 | 107 | side: THREE.DoubleSide 108 | 109 | } ); 110 | 111 | // generating geometry and add mesh to scene 112 | const geometry = new THREE.PlaneGeometry( 10, 10, 512, 512 ); 113 | mesh = new THREE.Mesh( geometry, material ); 114 | mesh.scale.y = 1.0 / image_ar; 115 | mesh.scale.multiplyScalar( 0.23 ); 116 | scene.add( mesh ); 117 | 118 | } 119 | image.src = MyTexture; 120 | 121 | 122 | // setup gui 123 | const gui = new GUI(); 124 | gui.add( settings, 'metalness' ).min( 0 ).max( 1 ).onChange( function ( value ) { 125 | material.metalness = value; 126 | } ); 127 | gui.add( settings, 'roughness' ).min( 0 ).max( 1 ).onChange( function ( value ) { 128 | material.roughness = value; 129 | } ); 130 | gui.add( settings, 'ambientIntensity' ).min( 0 ).max( 1 ).onChange( function ( value ) { 131 | ambientLight.intensity = value; 132 | } ); 133 | gui.add( settings, 'displacementScale' ).min( 0 ).max( 30.0 ).onChange( function ( value ) { 134 | material.displacementScale = value; 135 | } ); 136 | gui.add( settings, 'displacementBias' ).min( -10 ).max( 10 ).onChange( function ( value ) { 137 | 138 | material.displacementBias = value; 139 | } ); 140 | 141 | 142 | // setup drop zone 143 | var dropZone = document.getElementById('dropzone'); 144 | 145 | function showDropZone() { 146 | dropZone.style.display = "block"; 147 | } 148 | function hideDropZone() { 149 | dropZone.style.display = "none"; 150 | } 151 | 152 | function allowDrag(e) { 153 | if (true) { // Test that the item being dragged is a valid one 154 | e.dataTransfer.dropEffect = 'copy'; 155 | e.preventDefault(); 156 | } 157 | } 158 | 159 | function handleDrop(e) { 160 | e.preventDefault(); 161 | hideDropZone(); 162 | const fileList = event.dataTransfer.files; 163 | if (fileList.length > 0) { 164 | readImage(fileList[0]); 165 | } 166 | } 167 | 168 | function readImage(file) { 169 | const reader = new FileReader(); 170 | reader.addEventListener('load', (event) => { 171 | image.src = event.target.result; 172 | }); 173 | reader.readAsDataURL(file); 174 | } 175 | 176 | window.addEventListener('dragenter', function(e) { 177 | showDropZone(); 178 | }); 179 | dropZone.addEventListener('dragenter', allowDrag); 180 | dropZone.addEventListener('dragover', allowDrag); 181 | dropZone.addEventListener('dragleave', function(e) { 182 | console.log('dragleave'); 183 | hideDropZone(); 184 | }); 185 | dropZone.addEventListener('drop', handleDrop); 186 | 187 | 188 | // listen for messages 189 | window.addEventListener('message', function(e) { 190 | if (e.data?.imagedata) { 191 | image.src = e.data.imagedata; 192 | } 193 | }); 194 | --------------------------------------------------------------------------------