custom-geometry-heightmap.html 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. <!-- Licensed under a BSD license. See license.html for license -->
  2. <!DOCTYPE html>
  3. <html>
  4. <head>
  5. <meta charset="utf-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
  7. <title>Three.js - Custom Geometry - Heightmap</title>
  8. <style>
  9. html, body {
  10. height: 100%;
  11. margin: 0;
  12. }
  13. #c {
  14. width: 100%;
  15. height: 100%;
  16. display: block;
  17. }
  18. </style>
  19. </head>
  20. <body>
  21. <canvas id="c"></canvas>
  22. </body>
  23. <!-- Import maps polyfill -->
  24. <!-- Remove this when import maps will be widely supported -->
  25. <script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
  26. <script type="importmap">
  27. {
  28. "imports": {
  29. "three": "../../build/three.module.js",
  30. "three/addons/": "../../examples/jsm/"
  31. }
  32. }
  33. </script>
  34. <script type="module">
  35. import * as THREE from 'three';
  36. import {OrbitControls} from 'three/addons/controls/OrbitControls.js';
  37. function main() {
  38. const canvas = document.querySelector('#c');
  39. const renderer = new THREE.WebGLRenderer({antialias: true, canvas});
  40. const fov = 75;
  41. const aspect = 2; // the canvas default
  42. const near = 0.1;
  43. const far = 200;
  44. const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  45. camera.position.set(20, 20, 20);
  46. const controls = new OrbitControls(camera, canvas);
  47. controls.target.set(0, 0, 0);
  48. controls.update();
  49. const scene = new THREE.Scene();
  50. function addLight(...pos) {
  51. const color = 0xFFFFFF;
  52. const intensity = 1;
  53. const light = new THREE.DirectionalLight(color, intensity);
  54. light.position.set(...pos);
  55. scene.add(light);
  56. }
  57. addLight(-1, 2, 4);
  58. addLight(1, 2, -2);
  59. const imgLoader = new THREE.ImageLoader();
  60. imgLoader.load('resources/images/heightmap-96x64.png', createHeightmap);
  61. function createHeightmap(image) {
  62. // extract the data from the image by drawing it to a canvas
  63. // and calling getImageData
  64. const ctx = document.createElement('canvas').getContext('2d');
  65. const {width, height} = image;
  66. ctx.canvas.width = width;
  67. ctx.canvas.height = height;
  68. ctx.drawImage(image, 0, 0);
  69. const {data} = ctx.getImageData(0, 0, width, height);
  70. const geometry = new THREE.Geometry();
  71. const cellsAcross = width - 1;
  72. const cellsDeep = height - 1;
  73. for (let z = 0; z < cellsDeep; ++z) {
  74. for (let x = 0; x < cellsAcross; ++x) {
  75. // compute row offsets into the height data
  76. // we multiply by 4 because the data is R,G,B,A but we
  77. // only care about R
  78. const base0 = (z * width + x) * 4;
  79. const base1 = base0 + (width * 4);
  80. // look up the height for the for points
  81. // around this cell
  82. const h00 = data[base0] / 32;
  83. const h01 = data[base0 + 4] / 32;
  84. const h10 = data[base1] / 32;
  85. const h11 = data[base1 + 4] / 32;
  86. // compute the average height
  87. const hm = (h00 + h01 + h10 + h11) / 4;
  88. // the corner positions
  89. const x0 = x;
  90. const x1 = x + 1;
  91. const z0 = z;
  92. const z1 = z + 1;
  93. // remember the first index of these 5 vertices
  94. const ndx = geometry.vertices.length;
  95. // add the 4 corners for this cell and the midpoint
  96. geometry.vertices.push(
  97. new THREE.Vector3(x0, h00, z0),
  98. new THREE.Vector3(x1, h01, z0),
  99. new THREE.Vector3(x0, h10, z1),
  100. new THREE.Vector3(x1, h11, z1),
  101. new THREE.Vector3((x0 + x1) / 2, hm, (z0 + z1) / 2),
  102. );
  103. // 2----3
  104. // |\ /|
  105. // | \/4|
  106. // | /\ |
  107. // |/ \|
  108. // 0----1
  109. // create 4 triangles
  110. geometry.faces.push(
  111. new THREE.Face3(ndx , ndx + 4, ndx + 1),
  112. new THREE.Face3(ndx + 1, ndx + 4, ndx + 3),
  113. new THREE.Face3(ndx + 3, ndx + 4, ndx + 2),
  114. new THREE.Face3(ndx + 2, ndx + 4, ndx + 0),
  115. );
  116. // add the texture coordinates for each vertex of each face.
  117. const u0 = x / cellsAcross;
  118. const v0 = z / cellsDeep;
  119. const u1 = (x + 1) / cellsAcross;
  120. const v1 = (z + 1) / cellsDeep;
  121. const um = (u0 + u1) / 2;
  122. const vm = (v0 + v1) / 2;
  123. geometry.faceVertexUvs[0].push(
  124. [ new THREE.Vector2(u0, v0), new THREE.Vector2(um, vm), new THREE.Vector2(u1, v0) ],
  125. [ new THREE.Vector2(u1, v0), new THREE.Vector2(um, vm), new THREE.Vector2(u1, v1) ],
  126. [ new THREE.Vector2(u1, v1), new THREE.Vector2(um, vm), new THREE.Vector2(u0, v1) ],
  127. [ new THREE.Vector2(u0, v1), new THREE.Vector2(um, vm), new THREE.Vector2(u0, v0) ],
  128. );
  129. }
  130. }
  131. geometry.computeFaceNormals();
  132. // center the geometry
  133. geometry.translate(width / -2, 0, height / -2);
  134. const loader = new THREE.TextureLoader();
  135. const texture = loader.load('resources/images/star.png');
  136. const material = new THREE.MeshPhongMaterial({color: 'green', map: texture});
  137. const cube = new THREE.Mesh(geometry, material);
  138. scene.add(cube);
  139. }
  140. function resizeRendererToDisplaySize(renderer) {
  141. const canvas = renderer.domElement;
  142. const width = canvas.clientWidth;
  143. const height = canvas.clientHeight;
  144. const needResize = canvas.width !== width || canvas.height !== height;
  145. if (needResize) {
  146. renderer.setSize(width, height, false);
  147. }
  148. return needResize;
  149. }
  150. function render() {
  151. if (resizeRendererToDisplaySize(renderer)) {
  152. const canvas = renderer.domElement;
  153. camera.aspect = canvas.clientWidth / canvas.clientHeight;
  154. camera.updateProjectionMatrix();
  155. }
  156. renderer.render(scene, camera);
  157. requestAnimationFrame(render);
  158. }
  159. requestAnimationFrame(render);
  160. }
  161. main();
  162. </script>
  163. </html>