webgpu_tsl_wood.html 11 KB

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