threejs-materials.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. import * as THREE from 'three';
  2. import {threejsLessonUtils} from './threejs-lesson-utils.js';
  3. {
  4. function makeSphere(widthDivisions, heightDivisions) {
  5. const radius = 7;
  6. return new THREE.SphereGeometry(radius, widthDivisions, heightDivisions);
  7. }
  8. const highPolySphereGeometry = function() {
  9. const widthDivisions = 100;
  10. const heightDivisions = 50;
  11. return makeSphere(widthDivisions, heightDivisions);
  12. }();
  13. const lowPolySphereGeometry = function() {
  14. const widthDivisions = 12;
  15. const heightDivisions = 9;
  16. return makeSphere(widthDivisions, heightDivisions);
  17. }();
  18. function smoothOrFlat(flatShading, radius = 7) {
  19. const widthDivisions = 12;
  20. const heightDivisions = 9;
  21. const geometry = new THREE.SphereGeometry(radius, widthDivisions, heightDivisions);
  22. const material = new THREE.MeshPhongMaterial({
  23. flatShading,
  24. color: 'hsl(300,50%,50%)',
  25. });
  26. return new THREE.Mesh(geometry, material);
  27. }
  28. function basicLambertPhongExample(MaterialCtor, lowPoly, params = {}) {
  29. const geometry = lowPoly ? lowPolySphereGeometry : highPolySphereGeometry;
  30. const material = new MaterialCtor({
  31. color: 'hsl(210,50%,50%)',
  32. ...params,
  33. });
  34. return {
  35. obj3D: new THREE.Mesh(geometry, material),
  36. trackball: lowPoly,
  37. };
  38. }
  39. function sideExample(side) {
  40. const base = new THREE.Object3D();
  41. const size = 6;
  42. const geometry = new THREE.PlaneGeometry(size, size);
  43. [
  44. { position: [ -1, 0, 0], up: [0, 1, 0], },
  45. { position: [ 1, 0, 0], up: [0, -1, 0], },
  46. { position: [ 0, -1, 0], up: [0, 0, -1], },
  47. { position: [ 0, 1, 0], up: [0, 0, 1], },
  48. { position: [ 0, 0, -1], up: [ 1, 0, 0], },
  49. { position: [ 0, 0, 1], up: [-1, 0, 0], },
  50. ].forEach((settings, ndx) => {
  51. const material = new THREE.MeshBasicMaterial({side});
  52. material.color.setHSL(ndx / 6, .5, .5);
  53. const mesh = new THREE.Mesh(geometry, material);
  54. mesh.up.set(...settings.up);
  55. mesh.lookAt(...settings.position);
  56. mesh.position.set(...settings.position).multiplyScalar(size * .75);
  57. base.add(mesh);
  58. });
  59. return base;
  60. }
  61. function makeStandardPhysicalMaterialGrid(elem, physical, update) {
  62. const numMetal = 5;
  63. const numRough = 7;
  64. const meshes = [];
  65. const MatCtor = physical ? THREE.MeshPhysicalMaterial : THREE.MeshStandardMaterial;
  66. const color = physical ? 'hsl(160,50%,50%)' : 'hsl(140,50%,50%)';
  67. for (let m = 0; m < numMetal; ++m) {
  68. const row = [];
  69. for (let r = 0; r < numRough; ++r) {
  70. const material = new MatCtor({
  71. color,
  72. roughness: r / (numRough - 1),
  73. metalness: 1 - m / (numMetal - 1),
  74. });
  75. const mesh = new THREE.Mesh(highPolySphereGeometry, material);
  76. row.push(mesh);
  77. }
  78. meshes.push(row);
  79. }
  80. return {
  81. obj3D: null,
  82. trackball: false,
  83. render(renderInfo) {
  84. const {camera, scene, renderer} = renderInfo;
  85. const rect = elem.getBoundingClientRect();
  86. const width = (rect.right - rect.left) * renderInfo.pixelRatio;
  87. const height = (rect.bottom - rect.top) * renderInfo.pixelRatio;
  88. const left = rect.left * renderInfo.pixelRatio;
  89. const bottom = (renderer.domElement.clientHeight - rect.bottom) * renderInfo.pixelRatio;
  90. const cellSize = Math.min(width / numRough, height / numMetal) | 0;
  91. const xOff = (width - cellSize * numRough) / 2;
  92. const yOff = (height - cellSize * numMetal) / 2;
  93. camera.aspect = 1;
  94. camera.updateProjectionMatrix();
  95. if (update) {
  96. update(meshes);
  97. }
  98. for (let m = 0; m < numMetal; ++m) {
  99. for (let r = 0; r < numRough; ++r) {
  100. const x = left + xOff + r * cellSize;
  101. const y = bottom + yOff + m * cellSize;
  102. renderer.setViewport(x, y, cellSize, cellSize);
  103. renderer.setScissor(x, y, cellSize, cellSize);
  104. const mesh = meshes[m][r];
  105. scene.add(mesh);
  106. renderer.render(scene, camera);
  107. scene.remove(mesh);
  108. }
  109. }
  110. },
  111. };
  112. }
  113. threejsLessonUtils.addDiagrams({
  114. smoothShading: {
  115. create() {
  116. return smoothOrFlat(false);
  117. },
  118. },
  119. flatShading: {
  120. create() {
  121. return smoothOrFlat(true);
  122. },
  123. },
  124. MeshBasicMaterial: {
  125. create() {
  126. return basicLambertPhongExample(THREE.MeshBasicMaterial);
  127. },
  128. },
  129. MeshLambertMaterial: {
  130. create() {
  131. return basicLambertPhongExample(THREE.MeshLambertMaterial);
  132. },
  133. },
  134. MeshPhongMaterial: {
  135. create() {
  136. return basicLambertPhongExample(THREE.MeshPhongMaterial);
  137. },
  138. },
  139. MeshBasicMaterialLowPoly: {
  140. create() {
  141. return basicLambertPhongExample(THREE.MeshBasicMaterial, true);
  142. },
  143. },
  144. MeshLambertMaterialLowPoly: {
  145. create() {
  146. return basicLambertPhongExample(THREE.MeshLambertMaterial, true);
  147. },
  148. },
  149. MeshPhongMaterialLowPoly: {
  150. create() {
  151. return basicLambertPhongExample(THREE.MeshPhongMaterial, true);
  152. },
  153. },
  154. MeshPhongMaterialShininess0: {
  155. create() {
  156. return basicLambertPhongExample(THREE.MeshPhongMaterial, false, {
  157. color: 'red',
  158. shininess: 0,
  159. });
  160. },
  161. },
  162. MeshPhongMaterialShininess30: {
  163. create() {
  164. return basicLambertPhongExample(THREE.MeshPhongMaterial, false, {
  165. color: 'red',
  166. shininess: 30,
  167. });
  168. },
  169. },
  170. MeshPhongMaterialShininess150: {
  171. create() {
  172. return basicLambertPhongExample(THREE.MeshPhongMaterial, false, {
  173. color: 'red',
  174. shininess: 150,
  175. });
  176. },
  177. },
  178. MeshBasicMaterialCompare: {
  179. create() {
  180. return basicLambertPhongExample(THREE.MeshBasicMaterial, false, {
  181. color: 'purple',
  182. });
  183. },
  184. },
  185. MeshLambertMaterialCompare: {
  186. create() {
  187. return basicLambertPhongExample(THREE.MeshLambertMaterial, false, {
  188. color: 'black',
  189. emissive: 'purple',
  190. });
  191. },
  192. },
  193. MeshPhongMaterialCompare: {
  194. create() {
  195. return basicLambertPhongExample(THREE.MeshPhongMaterial, false, {
  196. color: 'black',
  197. emissive: 'purple',
  198. shininess: 0,
  199. });
  200. },
  201. },
  202. MeshToonMaterial: {
  203. create() {
  204. return basicLambertPhongExample(THREE.MeshToonMaterial);
  205. },
  206. },
  207. MeshStandardMaterial: {
  208. create(props) {
  209. return makeStandardPhysicalMaterialGrid(props.renderInfo.elem, false);
  210. },
  211. },
  212. MeshPhysicalMaterial: {
  213. create(props) {
  214. const settings = {
  215. clearcoat: .5,
  216. clearcoatRoughness: 0,
  217. };
  218. function addElem(parent, type, style = {}) {
  219. const elem = document.createElement(type);
  220. Object.assign(elem.style, style);
  221. parent.appendChild(elem);
  222. return elem;
  223. }
  224. function addRange(elem, obj, prop, min, max) {
  225. const outer = addElem(elem, 'div', {
  226. width: '100%',
  227. textAlign: 'center',
  228. 'font-family': 'monospace',
  229. });
  230. const div = addElem(outer, 'div', {
  231. textAlign: 'left',
  232. display: 'inline-block',
  233. });
  234. const label = addElem(div, 'label', {
  235. display: 'inline-block',
  236. width: '12em',
  237. });
  238. label.textContent = prop;
  239. const num = addElem(div, 'div', {
  240. display: 'inline-block',
  241. width: '3em',
  242. });
  243. function updateNum() {
  244. num.textContent = obj[prop].toFixed(2);
  245. }
  246. updateNum();
  247. const input = addElem(div, 'input', {
  248. });
  249. Object.assign(input, {
  250. type: 'range',
  251. min: 0,
  252. max: 100,
  253. value: (obj[prop] - min) / (max - min) * 100,
  254. });
  255. input.addEventListener('input', () => {
  256. obj[prop] = min + (max - min) * input.value / 100;
  257. updateNum();
  258. });
  259. }
  260. const {elem} = props.renderInfo;
  261. addRange(elem, settings, 'clearcoat', 0, 1);
  262. addRange(elem, settings, 'clearcoatRoughness', 0, 1);
  263. const area = addElem(elem, 'div', {
  264. width: '100%',
  265. height: '400px',
  266. });
  267. return makeStandardPhysicalMaterialGrid(area, true, (meshes) => {
  268. meshes.forEach(row => row.forEach(mesh => {
  269. mesh.material.clearcoat = settings.clearcoat;
  270. mesh.material.clearcoatRoughness = settings.clearcoatRoughness;
  271. }));
  272. });
  273. },
  274. },
  275. MeshDepthMaterial: {
  276. create(props) {
  277. const {camera} = props;
  278. const radius = 4;
  279. const tube = 1.5;
  280. const radialSegments = 8;
  281. const tubularSegments = 64;
  282. const p = 2;
  283. const q = 3;
  284. const geometry = new THREE.TorusKnotGeometry(radius, tube, tubularSegments, radialSegments, p, q);
  285. const material = new THREE.MeshDepthMaterial();
  286. camera.near = 7;
  287. camera.far = 20;
  288. return new THREE.Mesh(geometry, material);
  289. },
  290. },
  291. MeshNormalMaterial: {
  292. create() {
  293. const radius = 4;
  294. const tube = 1.5;
  295. const radialSegments = 8;
  296. const tubularSegments = 64;
  297. const p = 2;
  298. const q = 3;
  299. const geometry = new THREE.TorusKnotGeometry(radius, tube, tubularSegments, radialSegments, p, q);
  300. const material = new THREE.MeshNormalMaterial();
  301. return new THREE.Mesh(geometry, material);
  302. },
  303. },
  304. sideDefault: {
  305. create() {
  306. return sideExample(THREE.FrontSide);
  307. },
  308. },
  309. sideDouble: {
  310. create() {
  311. return sideExample(THREE.DoubleSide);
  312. },
  313. },
  314. });
  315. }