1
0

threejs-materials.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  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. }