WoodNodeMaterial.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. import * as THREE from 'three';
  2. import * as TSL from 'three/tsl';
  3. // some helpers below are ported from Blender and converted to TSL
  4. const mapRange = TSL.Fn( ( [ x, fromMin, fromMax, toMin, toMax, clmp ] ) => {
  5. const factor = x.sub( fromMin ).div( fromMax.sub( fromMin ) );
  6. const result = toMin.add( factor.mul( toMax.sub( toMin ) ) );
  7. return TSL.select( clmp, TSL.max( TSL.min( result, toMax ), toMin ), result );
  8. } );
  9. const voronoi3d = TSL.wgslFn( `
  10. fn voronoi3d(x: vec3<f32>, smoothness: f32, randomness: f32) -> f32
  11. {
  12. let p = floor(x);
  13. let f = fract(x);
  14. var res = 0.0;
  15. var totalWeight = 0.0;
  16. for (var k = -1; k <= 1; k++)
  17. {
  18. for (var j = -1; j <= 1; j++)
  19. {
  20. for (var i = -1; i <= 1; i++)
  21. {
  22. let b = vec3<f32>(f32(i), f32(j), f32(k));
  23. let hashOffset = hash3d(p + b) * randomness;
  24. let r = b - f + hashOffset;
  25. let d = length(r);
  26. let weight = exp(-d * d / max(smoothness * smoothness, 0.001));
  27. res += d * weight;
  28. totalWeight += weight;
  29. }
  30. }
  31. }
  32. if (totalWeight > 0.0)
  33. {
  34. res /= totalWeight;
  35. }
  36. return smoothstep(0.0, 1.0, res);
  37. }
  38. fn hash3d(p: vec3<f32>) -> vec3<f32>
  39. {
  40. var p3 = fract(p * vec3<f32>(0.1031, 0.1030, 0.0973));
  41. p3 += dot(p3, p3.yzx + 33.33);
  42. return fract((p3.xxy + p3.yzz) * p3.zyx);
  43. }
  44. ` );
  45. // const hash3d = TSL.Fn( ( [ p ] ) => {
  46. // const p3 = p.mul( TSL.vec3( 0.1031, 0.1030, 0.0973 ) ).fract();
  47. // const dotProduct = p3.dot( p3.yzx.add( 33.33 ) );
  48. // p3.addAssign( dotProduct );
  49. // return p3.xxy.add( p3.yzz ).mul( p3.zyx ).fract();
  50. // } );
  51. // const voronoi3d = TSL.Fn( ( [ x, smoothness, randomness ] ) => {
  52. // let p = TSL.floor(x);
  53. // let f = TSL.fract(x);
  54. // var res = TSL.float(0.0);
  55. // var totalWeight = TSL.float(0.0);
  56. // TSL.Loop( 3, 3, 3, ( { k, j, i } ) => {
  57. // let b = TSL.vec3(TSL.float(i).sub(1), TSL.float(j).sub(1), TSL.float(k).sub(1));
  58. // let hashOffset = hash3d(p.add(b)).mul(randomness);
  59. // let r = b.sub(f).add(hashOffset);
  60. // let d = TSL.length(r);
  61. // let weight = TSL.exp(d.negate().mul(d).div(TSL.max(smoothness.mul(smoothness), 0.001)));
  62. // res.addAssign(d.mul(weight));
  63. // totalWeight.addAssign(weight);
  64. // } );
  65. // res.assign(TSL.select(totalWeight.greaterThan(0.0), res.div(totalWeight), res));
  66. // return TSL.smoothstep(0.0, 1.0, res);
  67. // } );
  68. const softLightMix = TSL.Fn( ( [ t, col1, col2 ] ) => {
  69. const tm = TSL.float( 1.0 ).sub( t );
  70. const one = TSL.vec3( 1.0 );
  71. const scr = one.sub( one.sub( col2 ).mul( one.sub( col1 ) ) );
  72. return tm.mul( col1 ).add( t.mul( one.sub( col1 ).mul( col2 ).mul( col1 ).add( col1.mul( scr ) ) ) );
  73. } );
  74. const noiseFbm = TSL.Fn( ( [ p, detail, roughness, lacunarity, useNormalize ] ) => {
  75. const fscale = TSL.float( 1.0 ).toVar();
  76. const amp = TSL.float( 1.0 ).toVar();
  77. const maxamp = TSL.float( 0.0 ).toVar();
  78. const sum = TSL.float( 0.0 ).toVar();
  79. const iterations = detail.floor();
  80. TSL.Loop( iterations, () => {
  81. const t = TSL.mx_noise_float( p.mul( fscale ) );
  82. sum.addAssign( t.mul( amp ) );
  83. maxamp.addAssign( amp );
  84. amp.mulAssign( roughness );
  85. fscale.mulAssign( lacunarity );
  86. } );
  87. const rmd = detail.sub( iterations );
  88. const hasRemainder = rmd.greaterThan( 0.001 );
  89. return TSL.select(
  90. hasRemainder,
  91. TSL.select(
  92. useNormalize.equal( 1 ),
  93. ( () => {
  94. const t = TSL.mx_noise_float( p.mul( fscale ) );
  95. const sum2 = sum.add( t.mul( amp ) );
  96. const maxamp2 = maxamp.add( amp );
  97. const normalizedSum = sum.div( maxamp ).mul( 0.5 ).add( 0.5 );
  98. const normalizedSum2 = sum2.div( maxamp2 ).mul( 0.5 ).add( 0.5 );
  99. return TSL.mix( normalizedSum, normalizedSum2, rmd );
  100. } )(),
  101. ( () => {
  102. const t = TSL.mx_noise_float( p.mul( fscale ) );
  103. const sum2 = sum.add( t.mul( amp ) );
  104. return TSL.mix( sum, sum2, rmd );
  105. } )()
  106. ),
  107. TSL.select(
  108. useNormalize.equal( 1 ),
  109. sum.div( maxamp ).mul( 0.5 ).add( 0.5 ),
  110. sum
  111. )
  112. );
  113. } );
  114. const noiseFbm3d = TSL.Fn( ( [ p, detail, roughness, lacunarity, useNormalize ] ) => {
  115. const fscale = TSL.float( 1.0 ).toVar();
  116. const amp = TSL.float( 1.0 ).toVar();
  117. const maxamp = TSL.float( 0.0 ).toVar();
  118. const sum = TSL.vec3( 0.0 ).toVar();
  119. const iterations = detail.floor();
  120. TSL.Loop( iterations, () => {
  121. const t = TSL.mx_noise_vec3( p.mul( fscale ) );
  122. sum.addAssign( t.mul( amp ) );
  123. maxamp.addAssign( amp );
  124. amp.mulAssign( roughness );
  125. fscale.mulAssign( lacunarity );
  126. } );
  127. const rmd = detail.sub( iterations );
  128. const hasRemainder = rmd.greaterThan( 0.001 );
  129. return TSL.select(
  130. hasRemainder,
  131. TSL.select(
  132. useNormalize.equal( 1 ),
  133. ( () => {
  134. const t = TSL.mx_noise_vec3( p.mul( fscale ) );
  135. const sum2 = sum.add( t.mul( amp ) );
  136. const maxamp2 = maxamp.add( amp );
  137. const normalizedSum = sum.div( maxamp ).mul( 0.5 ).add( 0.5 );
  138. const normalizedSum2 = sum2.div( maxamp2 ).mul( 0.5 ).add( 0.5 );
  139. return TSL.mix( normalizedSum, normalizedSum2, rmd );
  140. } )(),
  141. ( () => {
  142. const t = TSL.mx_noise_vec3( p.mul( fscale ) );
  143. const sum2 = sum.add( t.mul( amp ) );
  144. return TSL.mix( sum, sum2, rmd );
  145. } )()
  146. ),
  147. TSL.select(
  148. useNormalize.equal( 1 ),
  149. sum.div( maxamp ).mul( 0.5 ).add( 0.5 ),
  150. sum
  151. )
  152. );
  153. } );
  154. const woodCenter = TSL.Fn( ( [ p, centerSize ] ) => {
  155. const pxyCenter = p.mul( TSL.vec3( 1, 1, 0 ) ).length();
  156. const center = mapRange( pxyCenter, 0, 1, 0, centerSize, true );
  157. return center;
  158. } );
  159. const spaceWarp = TSL.Fn( ( [ p, warpStrength, xyScale, zScale ] ) => {
  160. const combinedXyz = TSL.vec3( xyScale, xyScale, zScale ).mul( p );
  161. const noise = noiseFbm3d( combinedXyz.mul( 1.6 * 1.5 ), TSL.float( 1 ), TSL.float( 0.5 ), TSL.float( 2 ), TSL.int( 1 ) ).sub( 0.5 ).mul( warpStrength );
  162. const pXy = p.mul( TSL.vec3( 1, 1, 0 ) );
  163. const normalizedXy = pXy.normalize();
  164. const warp = noise.mul( normalizedXy ).add( pXy );
  165. return warp;
  166. } );
  167. const woodRings = TSL.Fn( ( [ w, ringThickness, ringBias, ringSizeVariance, ringVarianceScale, barkThickness ] ) => {
  168. const rings = noiseFbm( w.mul( ringVarianceScale ), TSL.float( 1 ), TSL.float( 0.5 ), TSL.float( 1 ), TSL.int( 1 ) ).mul( ringSizeVariance ).add( w ).mul( ringThickness ).fract().mul( barkThickness );
  169. const sharpRings = TSL.min( mapRange( rings, 0, ringBias, 0, 1, TSL.bool( true ) ), mapRange( rings, ringBias, 1, 1, 0, TSL.bool( true ) ) );
  170. const blurAmount = TSL.max( TSL.positionView.length().div( 10 ), 1 );
  171. const blurredRings = TSL.smoothstep( blurAmount.negate(), blurAmount, sharpRings.sub( 0.5 ) ).mul( 0.5 ).add( 0.5 );
  172. return blurredRings;
  173. } );
  174. const woodDetail = TSL.Fn( ( [ warp, p, y, splotchScale ] ) => {
  175. const radialCoords = TSL.clamp( TSL.atan( warp.y, warp.x ).div( TSL.PI2 ).add( 0.5 ), 0, 1 ).mul( TSL.PI2.mul( 3 ) );
  176. const combinedXyz = TSL.vec3( radialCoords.sin(), y, radialCoords.cos().mul( p.z ) );
  177. const scaled = TSL.vec3( 0.1, 1.19, 0.05 ).mul( combinedXyz );
  178. return noiseFbm( scaled.mul( splotchScale ), TSL.float( 1 ), TSL.float( 0.5 ), TSL.float( 2 ), TSL.bool( true ) );
  179. } );
  180. const cellStructure = TSL.Fn( ( [ p, cellScale, cellSize ] ) => {
  181. const warp = spaceWarp( p.mul( cellScale.div( 50 ) ), cellScale.div( 1000 ), 0.1, 1.77 );
  182. const cells = voronoi3d( warp.xy.mul( 75 ), 0.5, 1 );
  183. return mapRange( cells, cellSize, cellSize.add( 0.21 ), 0, 1, TSL.bool( true ) );
  184. } );
  185. const wood = TSL.Fn( ( [
  186. p,
  187. centerSize,
  188. largeWarpScale,
  189. largeGrainStretch,
  190. smallWarpStrength,
  191. smallWarpScale,
  192. fineWarpStrength,
  193. fineWarpScale,
  194. ringThickness,
  195. ringBias,
  196. ringSizeVariance,
  197. ringVarianceScale,
  198. barkThickness,
  199. splotchScale,
  200. splotchIntensity,
  201. cellScale,
  202. cellSize,
  203. darkGrainColor,
  204. lightGrainColor
  205. ] ) => {
  206. const center = woodCenter( p, centerSize );
  207. const mainWarp = spaceWarp( spaceWarp( p, center, largeWarpScale, largeGrainStretch ), smallWarpStrength, smallWarpScale, 0.17 );
  208. const detailWarp = spaceWarp( mainWarp, fineWarpStrength, fineWarpScale, 0.17 );
  209. const rings = woodRings( detailWarp.length(), TSL.float( 1 ).div( ringThickness ), ringBias, ringSizeVariance, ringVarianceScale, barkThickness );
  210. const detail = woodDetail( detailWarp, p, detailWarp.length(), splotchScale );
  211. const cells = cellStructure( mainWarp, cellScale, cellSize.div( TSL.max( TSL.positionView.length().mul( 10 ), 1 ) ) );
  212. const baseColor = TSL.mix( darkGrainColor, lightGrainColor, rings );
  213. return softLightMix( splotchIntensity, softLightMix( 0.407, baseColor, cells ), detail );
  214. } );
  215. const woodParams = {
  216. teak: {
  217. transformationMatrix: new THREE.Matrix4().identity(),
  218. centerSize: 1.11, largeWarpScale: 0.32, largeGrainStretch: 0.24, smallWarpStrength: 0.059,
  219. smallWarpScale: 2, fineWarpStrength: 0.006, fineWarpScale: 32.8, ringThickness: 1/34,
  220. ringBias: 0.03, ringSizeVariance: 0.03, ringVarianceScale: 4.4, barkThickness: 0.3,
  221. splotchScale: 0.2, splotchIntensity: 0.541, cellScale: 910, cellSize: 0.1,
  222. darkGrainColor: '#0c0504', lightGrainColor: '#926c50'
  223. },
  224. walnut: {
  225. transformationMatrix: new THREE.Matrix4().identity(),
  226. centerSize: 1.07, largeWarpScale: 0.42, largeGrainStretch: 0.34, smallWarpStrength: 0.016,
  227. smallWarpScale: 10.3, fineWarpStrength: 0.028, fineWarpScale: 12.7, ringThickness: 1/32,
  228. ringBias: 0.08, ringSizeVariance: 0.03, ringVarianceScale: 5.5, barkThickness: 0.98,
  229. splotchScale: 1.84, splotchIntensity: 0.97, cellScale: 710, cellSize: 0.31,
  230. darkGrainColor: '#311e13', lightGrainColor: '#523424'
  231. },
  232. white_oak: {
  233. transformationMatrix: new THREE.Matrix4().identity(),
  234. centerSize: 1.23, largeWarpScale: 0.21, largeGrainStretch: 0.21, smallWarpStrength: 0.034,
  235. smallWarpScale: 2.44, fineWarpStrength: 0.01, fineWarpScale: 14.3, ringThickness: 1/34,
  236. ringBias: 0.82, ringSizeVariance: 0.16, ringVarianceScale: 1.4, barkThickness: 0.7,
  237. splotchScale: 0.2, splotchIntensity: 0.541, cellScale: 800, cellSize: 0.28,
  238. darkGrainColor: '#8b4c21', lightGrainColor: '#c57e43'
  239. },
  240. pine: {
  241. transformationMatrix: new THREE.Matrix4().identity(),
  242. centerSize: 1.23, largeWarpScale: 0.21, largeGrainStretch: 0.18, smallWarpStrength: 0.041,
  243. smallWarpScale: 2.44, fineWarpStrength: 0.006, fineWarpScale: 23.2, ringThickness: 1/24,
  244. ringBias: 0.1, ringSizeVariance: 0.07, ringVarianceScale: 5, barkThickness: 0.35,
  245. splotchScale: 0.51, splotchIntensity: 3.32, cellScale: 1480, cellSize: 0.07,
  246. darkGrainColor: '#c58355', lightGrainColor: '#d19d61'
  247. },
  248. poplar: {
  249. transformationMatrix: new THREE.Matrix4().identity(),
  250. centerSize: 1.43, largeWarpScale: 0.33, largeGrainStretch: 0.18, smallWarpStrength: 0.04,
  251. smallWarpScale: 4.3, fineWarpStrength: 0.004, fineWarpScale: 33.6, ringThickness: 1/37,
  252. ringBias: 0.07, ringSizeVariance: 0.03, ringVarianceScale: 3.8, barkThickness: 0.3,
  253. splotchScale: 1.92, splotchIntensity: 0.71, cellScale: 830, cellSize: 0.04,
  254. darkGrainColor: '#716347', lightGrainColor: '#998966'
  255. },
  256. maple: {
  257. transformationMatrix: new THREE.Matrix4().identity(),
  258. centerSize: 1.4, largeWarpScale: 0.38, largeGrainStretch: 0.25, smallWarpStrength: 0.067,
  259. smallWarpScale: 2.5, fineWarpStrength: 0.005, fineWarpScale: 33.6, ringThickness: 1/35,
  260. ringBias: 0.1, ringSizeVariance: 0.07, ringVarianceScale: 4.6, barkThickness: 0.61,
  261. splotchScale: 0.46, splotchIntensity: 1.49, cellScale: 800, cellSize: 0.03,
  262. darkGrainColor: '#b08969', lightGrainColor: '#bc9d7d'
  263. },
  264. red_oak: {
  265. transformationMatrix: new THREE.Matrix4().identity(),
  266. centerSize: 1.21, largeWarpScale: 0.24, largeGrainStretch: 0.25, smallWarpStrength: 0.044,
  267. smallWarpScale: 2.54, fineWarpStrength: 0.01, fineWarpScale: 14.5, ringThickness: 1/34,
  268. ringBias: 0.92, ringSizeVariance: 0.03, ringVarianceScale: 5.6, barkThickness: 1.01,
  269. splotchScale: 0.28, splotchIntensity: 3.48, cellScale: 800, cellSize: 0.25,
  270. darkGrainColor: '#af613b', lightGrainColor: '#e0a27a'
  271. },
  272. cherry: {
  273. transformationMatrix: new THREE.Matrix4().identity(),
  274. centerSize: 1.33, largeWarpScale: 0.11, largeGrainStretch: 0.33, smallWarpStrength: 0.024,
  275. smallWarpScale: 2.48, fineWarpStrength: 0.01, fineWarpScale: 15.3, ringThickness: 1/36,
  276. ringBias: 0.02, ringSizeVariance: 0.04, ringVarianceScale: 6.5, barkThickness: 0.09,
  277. splotchScale: 1.27, splotchIntensity: 1.24, cellScale: 1530, cellSize: 0.15,
  278. darkGrainColor: '#913f27', lightGrainColor: '#b45837'
  279. },
  280. cedar: {
  281. transformationMatrix: new THREE.Matrix4().identity(),
  282. centerSize: 1.11, largeWarpScale: 0.39, largeGrainStretch: 0.12, smallWarpStrength: 0.061,
  283. smallWarpScale: 1.9, fineWarpStrength: 0.006, fineWarpScale: 4.8, ringThickness: 1/25,
  284. ringBias: 0.01, ringSizeVariance: 0.07, ringVarianceScale: 6.7, barkThickness: 0.1,
  285. splotchScale: 0.61, splotchIntensity: 2.54, cellScale: 630, cellSize: 0.19,
  286. darkGrainColor: '#9a5b49', lightGrainColor: '#ae745e'
  287. },
  288. mahogany: {
  289. transformationMatrix: new THREE.Matrix4().identity(),
  290. centerSize: 1.25, largeWarpScale: 0.26, largeGrainStretch: 0.29, smallWarpStrength: 0.044,
  291. smallWarpScale: 2.54, fineWarpStrength: 0.01, fineWarpScale: 15.3, ringThickness: 1/38,
  292. ringBias: 0.01, ringSizeVariance: 0.33, ringVarianceScale: 1.2, barkThickness: 0.07,
  293. splotchScale: 0.77, splotchIntensity: 1.39, cellScale: 1400, cellSize: 0.23,
  294. darkGrainColor: '#501d12', lightGrainColor: '#6d3722'
  295. }
  296. };
  297. export const WoodGenuses = [ 'teak', 'walnut', 'white_oak', 'pine', 'poplar', 'maple', 'red_oak', 'cherry', 'cedar', 'mahogany' ];
  298. export const Finishes = [ 'raw', 'matte', 'semigloss', 'gloss' ];
  299. export function GetWoodPreset( genus, finish ) {
  300. const params = woodParams[ genus ];
  301. let clearcoat, clearcoatRoughness, clearcoatDarken;
  302. switch ( finish ) {
  303. case 'gloss':
  304. clearcoatDarken = 0.2; clearcoatRoughness = 0.1; clearcoat = 1;
  305. break;
  306. case 'semigloss':
  307. clearcoatDarken = 0.4; clearcoatRoughness = 0.4; clearcoat = 1;
  308. break;
  309. case 'matte':
  310. clearcoatDarken = 0.6; clearcoatRoughness = 1; clearcoat = 1;
  311. break;
  312. case 'raw':
  313. default:
  314. clearcoatDarken = 1; clearcoatRoughness = 0; clearcoat = 0;
  315. }
  316. return { ...params, transformationMatrix: new THREE.Matrix4().copy( params.transformationMatrix ), genus, finish, clearcoat, clearcoatRoughness, clearcoatDarken };
  317. }
  318. const params = GetWoodPreset( WoodGenuses[ 0 ], Finishes[ 0 ] );
  319. const uniforms = {};
  320. uniforms.centerSize = TSL.uniform( params.centerSize ).onObjectUpdate( ( { material } ) => material.centerSize );
  321. uniforms.largeWarpScale = TSL.uniform( params.largeWarpScale ).onObjectUpdate( ( { material } ) => material.largeWarpScale );
  322. uniforms.largeGrainStretch = TSL.uniform( params.largeGrainStretch ).onObjectUpdate( ( { material } ) => material.largeGrainStretch );
  323. uniforms.smallWarpStrength = TSL.uniform( params.smallWarpStrength ).onObjectUpdate( ( { material } ) => material.smallWarpStrength );
  324. uniforms.smallWarpScale = TSL.uniform( params.smallWarpScale ).onObjectUpdate( ( { material } ) => material.smallWarpScale );
  325. uniforms.fineWarpStrength = TSL.uniform( params.fineWarpStrength ).onObjectUpdate( ( { material } ) => material.fineWarpStrength );
  326. uniforms.fineWarpScale = TSL.uniform( params.fineWarpScale ).onObjectUpdate( ( { material } ) => material.fineWarpScale );
  327. uniforms.ringThickness = TSL.uniform( params.ringThickness ).onObjectUpdate( ( { material } ) => material.ringThickness );
  328. uniforms.ringBias = TSL.uniform( params.ringBias ).onObjectUpdate( ( { material } ) => material.ringBias );
  329. uniforms.ringSizeVariance = TSL.uniform( params.ringSizeVariance ).onObjectUpdate( ( { material } ) => material.ringSizeVariance );
  330. uniforms.ringVarianceScale = TSL.uniform( params.ringVarianceScale ).onObjectUpdate( ( { material } ) => material.ringVarianceScale );
  331. uniforms.barkThickness = TSL.uniform( params.barkThickness ).onObjectUpdate( ( { material } ) => material.barkThickness );
  332. uniforms.splotchScale = TSL.uniform( params.splotchScale ).onObjectUpdate( ( { material } ) => material.splotchScale );
  333. uniforms.splotchIntensity = TSL.uniform( params.splotchIntensity ).onObjectUpdate( ( { material } ) => material.splotchIntensity );
  334. uniforms.cellScale = TSL.uniform( params.cellScale ).onObjectUpdate( ( { material } ) => material.cellScale );
  335. uniforms.cellSize = TSL.uniform( params.cellSize ).onObjectUpdate( ( { material } ) => material.cellSize );
  336. uniforms.darkGrainColor = TSL.uniform( new THREE.Color( params.darkGrainColor ) ).onObjectUpdate( ( { material }, self ) => self.value.set( material.darkGrainColor ) );
  337. uniforms.lightGrainColor = TSL.uniform( new THREE.Color( params.lightGrainColor ) ).onObjectUpdate( ( { material }, self ) => self.value.set( material.lightGrainColor ) );
  338. uniforms.transformationMatrix = TSL.uniform( new THREE.Matrix4().copy( params.transformationMatrix ) ).onObjectUpdate( ( { material } ) => material.transformationMatrix );
  339. const colorNode = wood(
  340. uniforms.transformationMatrix.mul( TSL.vec4(TSL.positionLocal, 1) ).xyz,
  341. uniforms.centerSize,
  342. uniforms.largeWarpScale,
  343. uniforms.largeGrainStretch,
  344. uniforms.smallWarpStrength,
  345. uniforms.smallWarpScale,
  346. uniforms.fineWarpStrength,
  347. uniforms.fineWarpScale,
  348. uniforms.ringThickness,
  349. uniforms.ringBias,
  350. uniforms.ringSizeVariance,
  351. uniforms.ringVarianceScale,
  352. uniforms.barkThickness,
  353. uniforms.splotchScale,
  354. uniforms.splotchIntensity,
  355. uniforms.cellScale,
  356. uniforms.cellSize,
  357. uniforms.darkGrainColor,
  358. uniforms.lightGrainColor
  359. ).mul( params.clearcoatDarken );
  360. /**
  361. * Procedural wood material using TSL (Three.js Shading Language).
  362. *
  363. * Usage examples:
  364. *
  365. * // Using presets (recommended for common wood types)
  366. * const material = WoodNodeMaterial.fromPreset('walnut', 'gloss');
  367. *
  368. * // Using custom parameters (for advanced customization)
  369. * const material = new WoodNodeMaterial({
  370. * centerSize: 1.2,
  371. * ringThickness: 1/40,
  372. * darkGrainColor: new THREE.Color('#2a1a0a'),
  373. * lightGrainColor: new THREE.Color('#8b4513'),
  374. * clearcoat: 1,
  375. * clearcoatRoughness: 0.3
  376. * });
  377. *
  378. * // Mixing presets with custom overrides
  379. * const walnutParams = GetWoodPreset('walnut', 'raw');
  380. * const material = new WoodNodeMaterial({
  381. * ...walnutParams,
  382. * ringThickness: 1/50, // Override specific parameter
  383. * clearcoat: 1 // Add finish
  384. * });
  385. */
  386. export class WoodNodeMaterial extends THREE.MeshPhysicalMaterial {
  387. static get type() {
  388. return 'WoodNodeMaterial';
  389. }
  390. constructor( params = {} ) {
  391. super();
  392. this.isWoodNodeMaterial = true;
  393. // Get default parameters from teak/raw preset
  394. const defaultParams = GetWoodPreset( 'teak', 'raw' );
  395. // Merge default params with provided params
  396. const finalParams = { ...defaultParams, ...params };
  397. for ( const key in finalParams ) {
  398. if ( key === 'genus' || key === 'finish' ) continue;
  399. if ( typeof finalParams[ key ] === 'string' ) {
  400. this[ key ] = new THREE.Color( finalParams[ key ] );
  401. } else {
  402. this[ key ] = finalParams[ key ];
  403. }
  404. }
  405. this.colorNode = colorNode;
  406. this.clearcoatNode = finalParams.clearcoat;
  407. this.clearcoatRoughness = finalParams.clearcoatRoughness;
  408. }
  409. // Static method to create material from preset
  410. static fromPreset( genus = 'teak', finish = 'raw' ) {
  411. const params = GetWoodPreset( genus, finish );
  412. return new WoodNodeMaterial( params );
  413. }
  414. }
粤ICP备19079148号