threejs-materials.js 9.8 KB

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