1
0

webgpu_tsl_wood.html 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>Three.js webgpu - procedural wood materials</title>
  6. <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  7. <meta name="author" content="Logan Seeley"/>
  8. <link type="text/css" rel="stylesheet" href="example.css">
  9. <style>
  10. body {
  11. color:rgb(55, 55, 55);
  12. }
  13. </style>
  14. </head>
  15. <body>
  16. <div id="info" class="invert">
  17. <a href="https://threejs.org/" target="_blank" rel="noopener" class="logo-link"></a>
  18. <div class="title-wrapper">
  19. <a href="https://threejs.org/" target="_blank" rel="noopener">three.js</a><span>Procedural Woord Material</span>
  20. </div>
  21. <small>
  22. By Logan Seeley, based on <a href="https://www.youtube.com/watch?v=n7e0vxgBS8A">Lance Phan's Blender tutorial.</a>
  23. </small>
  24. </div>
  25. <script type="importmap">
  26. {
  27. "imports": {
  28. "three": "../build/three.webgpu.js",
  29. "three/webgpu": "../build/three.webgpu.js",
  30. "three/tsl": "../build/three.tsl.js",
  31. "three/addons/": "./jsm/"
  32. }
  33. }
  34. </script>
  35. <script type="module">
  36. import * as THREE from 'three';
  37. import * as TSL from 'three/tsl';
  38. import { Inspector } from 'three/addons/inspector/Inspector.js';
  39. import WebGPU from 'three/addons/capabilities/WebGPU.js';
  40. import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
  41. import { HDRLoader } from 'three/addons/loaders/HDRLoader.js';
  42. import { FontLoader } from 'three/addons/loaders/FontLoader.js';
  43. import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
  44. import { RoundedBoxGeometry } from 'three/addons/geometries/RoundedBoxGeometry.js';
  45. import { WoodNodeMaterial, WoodGenuses, Finishes } from 'three/addons/materials/WoodNodeMaterial.js';
  46. let scene, base, camera, renderer, controls, font, blockGeometry, gui;
  47. // Helper function to get grid position
  48. function getGridPosition( woodIndex, finishIndex ) {
  49. return {
  50. x: 0,
  51. y: ( finishIndex - Finishes.length / 2 ) * 1.0,
  52. z: ( woodIndex - WoodGenuses.length / 2 + 0.45 ) * 1.0
  53. };
  54. }
  55. // Helper function to create the grid plane
  56. function createGridPlane() {
  57. const material = new THREE.MeshBasicNodeMaterial();
  58. const gridXZ = TSL.Fn( ( [ gridSize = TSL.float( 1.0 ), dotWidth = TSL.float( 0.1 ), lineWidth = TSL.float( 0.02 ) ] ) => {
  59. const coord = TSL.positionWorld.xz.div( gridSize );
  60. const grid = TSL.fract( coord );
  61. // Screen-space derivative for automatic antialiasing
  62. const fw = TSL.fwidth( coord );
  63. const smoothing = TSL.max( fw.x, fw.y ).mul( 0.5 );
  64. // Create squares at cell centers
  65. const squareDist = TSL.max( TSL.abs( grid.x.sub( 0.5 ) ), TSL.abs( grid.y.sub( 0.5 ) ) );
  66. const dots = TSL.smoothstep( dotWidth.add( smoothing ), dotWidth.sub( smoothing ), squareDist );
  67. // Create grid lines
  68. const lineX = TSL.smoothstep( lineWidth.add( smoothing ), lineWidth.sub( smoothing ), TSL.abs( grid.x.sub( 0.5 ) ) );
  69. const lineZ = TSL.smoothstep( lineWidth.add( smoothing ), lineWidth.sub( smoothing ), TSL.abs( grid.y.sub( 0.5 ) ) );
  70. const lines = TSL.max( lineX, lineZ );
  71. return TSL.max( dots, lines );
  72. } );
  73. const radialGradient = TSL.Fn( ( [ radius = TSL.float( 10.0 ), falloff = TSL.float( 1.0 ) ] ) => {
  74. return TSL.smoothstep( radius, radius.sub( falloff ), TSL.length( TSL.positionWorld ) );
  75. } );
  76. // Create grid pattern
  77. const gridPattern = gridXZ( 1.0, 0.03, 0.005 );
  78. const baseColor = TSL.vec4( 1.0, 1.0, 1.0, 0.0 );
  79. const gridColor = TSL.vec4( 0.5, 0.5, 0.5, 1.0 );
  80. // Mix base color with grid lines
  81. material.colorNode = gridPattern.mix( baseColor, gridColor ).mul( radialGradient( 30.0, 20.0 ) );
  82. material.transparent = true;
  83. const plane = new THREE.Mesh( new THREE.CircleGeometry( 40 ), material );
  84. plane.rotation.x = - Math.PI / 2;
  85. plane.renderOrder = - 1;
  86. return plane;
  87. }
  88. // Helper function to create and position labels
  89. function createLabel( text, font, material, position ) {
  90. const txt_geo = new TextGeometry( text, {
  91. font: font,
  92. size: 0.1,
  93. depth: 0.001,
  94. curveSegments: 12,
  95. bevelEnabled: false
  96. } );
  97. txt_geo.computeBoundingBox();
  98. const offx = - 0.5 * ( txt_geo.boundingBox.max.x - txt_geo.boundingBox.min.x );
  99. const offy = - 0.5 * ( txt_geo.boundingBox.max.y - txt_geo.boundingBox.min.y );
  100. const offz = - 0.5 * ( txt_geo.boundingBox.max.z - txt_geo.boundingBox.min.z );
  101. txt_geo.translate( offx, offy, offz );
  102. const label = new THREE.Group();
  103. const mesh = new THREE.Mesh( txt_geo );
  104. label.add( mesh );
  105. // Apply default rotation for labels
  106. label.rotateY( - Math.PI / 2 );
  107. label.children[ 0 ].material = material;
  108. label.position.copy( position );
  109. base.add( label );
  110. }
  111. async function init() {
  112. scene = new THREE.Scene();
  113. scene.background = new THREE.Color( 0xffffff );
  114. camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
  115. camera.position.set( - 0.1, 5, 0.548 );
  116. renderer = new THREE.WebGPURenderer( { antialias: true } );
  117. renderer.setPixelRatio( 1.0 ); // important for performance
  118. renderer.setSize( window.innerWidth, window.innerHeight );
  119. renderer.toneMapping = THREE.NeutralToneMapping;
  120. renderer.toneMappingExposure = 1.0;
  121. renderer.inspector = new Inspector();
  122. renderer.setAnimationLoop( render );
  123. document.body.appendChild( renderer.domElement );
  124. controls = new OrbitControls( camera, renderer.domElement );
  125. controls.target.set( 0, 0, 0.548 );
  126. gui = renderer.inspector.createParameters( 'Parameters' );
  127. font = await new FontLoader().loadAsync( './fonts/helvetiker_regular.typeface.json' );
  128. // Create shared block geometry
  129. blockGeometry = new RoundedBoxGeometry( 0.125, 0.9, 0.9, 10, 0.02 );
  130. base = new THREE.Group();
  131. base.rotation.set( 0, 0, - Math.PI / 2 );
  132. base.position.set( 0, 0, 0.548 );
  133. scene.add( base );
  134. const text_mat = new THREE.MeshStandardMaterial();
  135. text_mat.colorNode = TSL.color( '#000000' );
  136. // Create finish labels (using negative wood index for left column)
  137. for ( let y = 0; y < Finishes.length; y ++ ) {
  138. createLabel( Finishes[ y ], font, text_mat, getGridPosition( - 1, y ) );
  139. }
  140. // Create and add the grid plane
  141. const plane = createGridPlane();
  142. scene.add( plane );
  143. await new HDRLoader()
  144. .setPath( 'textures/equirectangular/' )
  145. .loadAsync( 'san_giuseppe_bridge_2k.hdr' ).then( ( texture ) => {
  146. texture.mapping = THREE.EquirectangularReflectionMapping;
  147. scene.environment = texture;
  148. scene.environmentIntensity = 2;
  149. } );
  150. // Create wood labels (using negative finish index for top row)
  151. for ( let x = 0; x < WoodGenuses.length; x ++ ) {
  152. createLabel( WoodGenuses[ x ], font, text_mat, getGridPosition( x, - 1 ) );
  153. }
  154. // Create wood blocks
  155. for ( let x = 0; x < WoodGenuses.length; x ++ ) {
  156. for ( let y = 0; y < Finishes.length; y ++ ) {
  157. const material = WoodNodeMaterial.fromPreset( WoodGenuses[ x ], Finishes[ y ] );
  158. const cube = new THREE.Mesh( blockGeometry, material );
  159. cube.position.copy( getGridPosition( x, y ) );
  160. material.transformationMatrix = new THREE.Matrix4().setPosition( new THREE.Vector3( - 0.1, 0, Math.random() ) );
  161. base.add( cube );
  162. await new Promise( resolve => setTimeout( resolve, 0 ) );
  163. }
  164. }
  165. add_custom_wood( text_mat );
  166. }
  167. function render() {
  168. controls.update();
  169. renderer.render( scene, camera );
  170. }
  171. window.addEventListener( 'resize', () => {
  172. camera.aspect = window.innerWidth / window.innerHeight;
  173. camera.updateProjectionMatrix();
  174. renderer.setSize( window.innerWidth, window.innerHeight );
  175. } );
  176. if ( WebGPU.isAvailable() ) {
  177. init();
  178. } else {
  179. document.body.appendChild( WebGPU.getErrorMessage() );
  180. }
  181. function add_custom_wood( text_mat ) {
  182. // Add "Custom" label (positioned at the end of the grid)
  183. createLabel( 'custom', font, text_mat, getGridPosition( Math.round( WoodGenuses.length / 2 - 1 ), 5 ) );
  184. // Create custom wood material with unique parameters
  185. const customMaterial = new WoodNodeMaterial( {
  186. centerSize: 1.11,
  187. largeWarpScale: 0.32,
  188. largeGrainStretch: 0.24,
  189. smallWarpStrength: 0.059,
  190. smallWarpScale: 2,
  191. fineWarpStrength: 0.006,
  192. fineWarpScale: 32.8,
  193. ringThickness: 1 / 34,
  194. ringBias: 0.03,
  195. ringSizeVariance: 0.03,
  196. ringVarianceScale: 4.4,
  197. barkThickness: 0.3,
  198. splotchScale: 0.2,
  199. splotchIntensity: 0.541,
  200. cellScale: 910,
  201. cellSize: 0.1,
  202. darkGrainColor: new THREE.Color( '#0c0504' ),
  203. lightGrainColor: new THREE.Color( '#926c50' ),
  204. clearcoat: 1,
  205. clearcoatRoughness: 0.2
  206. } );
  207. gui.add( customMaterial, 'centerSize', 0.0, 2.0, 0.01 ).name( 'centerSize' );
  208. gui.add( customMaterial, 'largeWarpScale', 0.0, 1.0, 0.001 ).name( 'largeWarpScale' );
  209. gui.add( customMaterial, 'largeGrainStretch', 0.0, 1.0, 0.001 ).name( 'largeGrainStretch' );
  210. gui.add( customMaterial, 'smallWarpStrength', 0.0, 0.2, 0.001 ).name( 'smallWarpStrength' );
  211. gui.add( customMaterial, 'smallWarpScale', 0.0, 5.0, 0.01 ).name( 'smallWarpScale' );
  212. gui.add( customMaterial, 'fineWarpStrength', 0.0, 0.05, 0.001 ).name( 'fineWarpStrength' );
  213. gui.add( customMaterial, 'fineWarpScale', 0.0, 50.0, 0.1 ).name( 'fineWarpScale' );
  214. gui.add( customMaterial, 'ringThickness', 0.0, 0.1, 0.001 ).name( 'ringThickness' );
  215. gui.add( customMaterial, 'ringBias', - 0.2, 0.2, 0.001 ).name( 'ringBias' );
  216. gui.add( customMaterial, 'ringSizeVariance', 0.0, 0.2, 0.001 ).name( 'ringSizeVariance' );
  217. gui.add( customMaterial, 'ringVarianceScale', 0.0, 10.0, 0.1 ).name( 'ringVarianceScale' );
  218. gui.add( customMaterial, 'barkThickness', 0.0, 1.0, 0.01 ).name( 'barkThickness' );
  219. gui.add( customMaterial, 'splotchScale', 0.0, 1.0, 0.01 ).name( 'splotchScale' );
  220. gui.add( customMaterial, 'splotchIntensity', 0.0, 1.0, 0.01 ).name( 'splotchIntensity' );
  221. gui.add( customMaterial, 'cellScale', 100, 2000, 1 ).name( 'cellScale' );
  222. gui.add( customMaterial, 'cellSize', 0.01, 0.5, 0.001 ).name( 'cellSize' );
  223. gui.addColor( { darkGrainColor: '#0c0504' }, 'darkGrainColor' ).onChange( v => customMaterial.darkGrainColor.set( v ) );
  224. gui.addColor( { lightGrainColor: '#926c50' }, 'lightGrainColor' ).onChange( v => customMaterial.lightGrainColor.set( v ) );
  225. gui.add( customMaterial, 'clearcoat', 0.0, 1.0, 0.01 ).name( 'clearcoat' );
  226. gui.add( customMaterial, 'clearcoatRoughness', 0.0, 1.0, 0.01 ).name( 'clearcoatRoughness' );
  227. const cube = new THREE.Mesh( blockGeometry, customMaterial );
  228. customMaterial.transformationMatrix = new THREE.Matrix4().setPosition( new THREE.Vector3( - 0.1, 0, Math.random() ) );
  229. cube.position.copy( getGridPosition( Math.round( WoodGenuses.length / 2 ), 5 ) );
  230. base.add( cube );
  231. }
  232. </script>
  233. </body>
  234. </html>
粤ICP备19079148号