|
6 | 6 | <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
7 | 7 | <link type="text/css" rel="stylesheet" href="main.css">
|
8 | 8 | </head>
|
9 |
| - |
10 | 9 | <body>
|
| 10 | + |
11 | 11 | <div id="info">
|
12 |
| - <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - 3D LUTs<br /> |
13 |
| - Battle Damaged Sci-fi Helmet by |
14 |
| - <a href="https://sketchfab.com/theblueturtle_" target="_blank" rel="noopener">theblueturtle_</a><br /> |
15 |
| - <a href="https://hdrihaven.com/hdri/?h=royal_esplanade" target="_blank" rel="noopener">Royal Esplanade</a> from <a href="https://hdrihaven.com/" target="_blank" rel="noopener">HDRI Haven</a><br /> |
| 12 | + <a href="https://threejs.org" target="_blank" rel="noopener">three.js webgpu</a> - post processing - 3D LUTs<br /> |
| 13 | + Based on <a href="https://threejs-journey.com/lessons/coffee-smoke-shader" target="_blank" rel="noopener">Three.js Journey</a> lesson<br /> |
| 14 | + Perlin noise texture from <a href="http://kitfox.com/projects/perlinNoiseMaker/" target="_blank" rel="noopener">Perlin Noise Maker</a>, |
16 | 15 | LUTs from <a href="https://www.rocketstock.com/free-after-effects-templates/35-free-luts-for-color-grading-videos/">RocketStock</a>, <a href="https://www.freepresets.com/product/free-luts-cinematic/">FreePresets.com</a>
|
17 | 16 | </div>
|
18 | 17 |
|
|
30 | 29 | <script type="module">
|
31 | 30 |
|
32 | 31 | import * as THREE from 'three';
|
33 |
| - import { pass, texture3D, uniform, renderOutput } from 'three/tsl'; |
| 32 | + import { mix, mul, oneMinus, positionLocal, smoothstep, texture, time, rotateUV, Fn, uv, vec2, vec3, vec4, pass, texture3D, uniform, renderOutput } from 'three/tsl'; |
34 | 33 | import { lut3D } from 'three/addons/tsl/display/Lut3DNode.js';
|
35 | 34 |
|
36 | 35 | import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
37 | 36 | import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
38 |
| - import { RGBELoader } from 'three/addons/loaders/RGBELoader.js'; |
39 | 37 | import { LUTCubeLoader } from 'three/addons/loaders/LUTCubeLoader.js';
|
40 | 38 | import { LUT3dlLoader } from 'three/addons/loaders/LUT3dlLoader.js';
|
41 | 39 | import { LUTImageLoader } from 'three/addons/loaders/LUTImageLoader.js';
|
|
58 | 56 | 'NightLUT': null
|
59 | 57 | };
|
60 | 58 |
|
61 |
| - let gui; |
62 |
| - let camera, scene, renderer; |
63 |
| - let postProcessing, lutPass; |
| 59 | + let camera, scene, renderer, postProcessing, controls, lutPass; |
64 | 60 |
|
65 | 61 | init();
|
66 | 62 |
|
67 | 63 | async function init() {
|
68 | 64 |
|
69 |
| - const container = document.createElement( 'div' ); |
70 |
| - document.body.appendChild( container ); |
71 |
| - |
72 |
| - camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.25, 20 ); |
73 |
| - camera.position.set( - 1.8, 0.6, 2.7 ); |
| 65 | + camera = new THREE.PerspectiveCamera( 25, window.innerWidth / window.innerHeight, 0.1, 100 ); |
| 66 | + camera.position.set( 8, 10, 12 ); |
74 | 67 |
|
75 | 68 | scene = new THREE.Scene();
|
76 | 69 |
|
77 |
| - new RGBELoader() |
78 |
| - .setPath( 'textures/equirectangular/' ) |
79 |
| - .load( 'royal_esplanade_1k.hdr', function ( texture ) { |
80 |
| - |
81 |
| - texture.mapping = THREE.EquirectangularReflectionMapping; |
82 |
| - |
83 |
| - scene.background = texture; |
84 |
| - scene.environment = texture; |
85 |
| - |
86 |
| - // model |
| 70 | + // Loaders |
87 | 71 |
|
88 |
| - const loader = new GLTFLoader().setPath( 'models/gltf/DamagedHelmet/glTF/' ); |
89 |
| - loader.load( 'DamagedHelmet.gltf', function ( gltf ) { |
| 72 | + const gltfLoader = new GLTFLoader(); |
| 73 | + const textureLoader = new THREE.TextureLoader(); |
90 | 74 |
|
91 |
| - scene.add( gltf.scene ); |
92 |
| - |
93 |
| - } ); |
94 |
| - |
95 |
| - } ); |
| 75 | + // LUTs |
96 | 76 |
|
97 | 77 | const lutCubeLoader = new LUTCubeLoader();
|
98 | 78 | const lutImageLoader = new LUTImageLoader();
|
|
125 | 105 |
|
126 | 106 | }
|
127 | 107 |
|
128 |
| - renderer = new THREE.WebGPURenderer(); |
| 108 | + // baked model |
| 109 | + |
| 110 | + gltfLoader.load( |
| 111 | + './models/gltf/coffeeMug.glb', |
| 112 | + ( gltf ) => { |
| 113 | + |
| 114 | + gltf.scene.getObjectByName( 'baked' ).material.map.anisotropy = 8; |
| 115 | + scene.add( gltf.scene ); |
| 116 | + |
| 117 | + } |
| 118 | + ); |
| 119 | + |
| 120 | + // geometry |
| 121 | + |
| 122 | + const smokeGeometry = new THREE.PlaneGeometry( 1, 1, 16, 64 ); |
| 123 | + smokeGeometry.translate( 0, 0.5, 0 ); |
| 124 | + smokeGeometry.scale( 1.5, 6, 1.5 ); |
| 125 | + |
| 126 | + // texture |
| 127 | + |
| 128 | + const noiseTexture = textureLoader.load( './textures/noises/perlin/128x128.png' ); |
| 129 | + noiseTexture.wrapS = THREE.RepeatWrapping; |
| 130 | + noiseTexture.wrapT = THREE.RepeatWrapping; |
| 131 | + |
| 132 | + // material |
| 133 | + |
| 134 | + const smokeMaterial = new THREE.MeshBasicNodeMaterial( { transparent: true, side: THREE.DoubleSide, depthWrite: false } ); |
| 135 | + |
| 136 | + // position |
| 137 | + |
| 138 | + smokeMaterial.positionNode = Fn( () => { |
| 139 | + |
| 140 | + // twist |
| 141 | + |
| 142 | + const twistNoiseUv = vec2( 0.5, uv().y.mul( 0.2 ).sub( time.mul( 0.005 ) ).mod( 1 ) ); |
| 143 | + const twist = texture( noiseTexture, twistNoiseUv ).r.mul( 10 ); |
| 144 | + positionLocal.xz.assign( rotateUV( positionLocal.xz, twist, vec2( 0 ) ) ); |
| 145 | + |
| 146 | + // wind |
| 147 | + |
| 148 | + const windOffset = vec2( |
| 149 | + texture( noiseTexture, vec2( 0.25, time.mul( 0.01 ) ).mod( 1 ) ).r.sub( 0.5 ), |
| 150 | + texture( noiseTexture, vec2( 0.75, time.mul( 0.01 ) ).mod( 1 ) ).r.sub( 0.5 ), |
| 151 | + ).mul( uv().y.pow( 2 ).mul( 10 ) ); |
| 152 | + positionLocal.addAssign( windOffset ); |
| 153 | + |
| 154 | + return positionLocal; |
| 155 | + |
| 156 | + } )(); |
| 157 | + |
| 158 | + // color |
| 159 | + |
| 160 | + smokeMaterial.colorNode = Fn( () => { |
| 161 | + |
| 162 | + // alpha |
| 163 | + |
| 164 | + const alphaNoiseUv = uv().mul( vec2( 0.5, 0.3 ) ).add( vec2( 0, time.mul( 0.03 ).negate() ) ); |
| 165 | + const alpha = mul( |
| 166 | + |
| 167 | + // pattern |
| 168 | + texture( noiseTexture, alphaNoiseUv ).r.smoothstep( 0.4, 1 ), |
| 169 | + |
| 170 | + // edges fade |
| 171 | + smoothstep( 0, 0.1, uv().x ), |
| 172 | + smoothstep( 0, 0.1, oneMinus( uv().x ) ), |
| 173 | + smoothstep( 0, 0.1, uv().y ), |
| 174 | + smoothstep( 0, 0.1, oneMinus( uv().y ) ) |
| 175 | + |
| 176 | + ); |
| 177 | + |
| 178 | + // color |
| 179 | + |
| 180 | + const finalColor = mix( vec3( 0.6, 0.3, 0.2 ), vec3( 1, 1, 1 ), alpha.pow( 3 ) ); |
| 181 | + |
| 182 | + return vec4( finalColor, alpha ); |
| 183 | + |
| 184 | + } )(); |
| 185 | + |
| 186 | + // mesh |
| 187 | + |
| 188 | + const smoke = new THREE.Mesh( smokeGeometry, smokeMaterial ); |
| 189 | + smoke.position.y = 1.83; |
| 190 | + scene.add( smoke ); |
| 191 | + |
| 192 | + // renderer |
| 193 | + |
| 194 | + renderer = new THREE.WebGPURenderer( { antialias: true } ); |
129 | 195 | renderer.setPixelRatio( window.devicePixelRatio );
|
130 | 196 | renderer.setSize( window.innerWidth, window.innerHeight );
|
131 | 197 | renderer.setAnimationLoop( animate );
|
132 |
| - renderer.toneMapping = THREE.ACESFilmicToneMapping; |
133 |
| - container.appendChild( renderer.domElement ); |
| 198 | + document.body.appendChild( renderer.domElement ); |
134 | 199 |
|
135 | 200 | // post processing
|
136 | 201 |
|
|
151 | 216 |
|
152 | 217 | postProcessing.outputNode = lutPass;
|
153 | 218 |
|
154 |
| - // |
| 219 | + // controls |
155 | 220 |
|
156 |
| - const controls = new OrbitControls( camera, renderer.domElement ); |
157 |
| - controls.minDistance = 2; |
158 |
| - controls.maxDistance = 10; |
159 |
| - controls.target.set( 0, 0, - 0.2 ); |
160 |
| - controls.update(); |
| 221 | + controls = new OrbitControls( camera, renderer.domElement ); |
| 222 | + controls.enableDamping = true; |
| 223 | + controls.minDistance = 0.1; |
| 224 | + controls.maxDistance = 50; |
| 225 | + controls.target.y = 3; |
| 226 | + |
| 227 | + // gui |
161 | 228 |
|
162 |
| - gui = new GUI(); |
| 229 | + const gui = new GUI(); |
163 | 230 | gui.add( params, 'lut', Object.keys( lutMap ) );
|
164 | 231 | gui.add( params, 'intensity' ).min( 0 ).max( 1 );
|
165 | 232 |
|
|
176 | 243 |
|
177 | 244 | }
|
178 | 245 |
|
179 |
| - // |
| 246 | + async function animate() { |
180 | 247 |
|
181 |
| - function animate() { |
| 248 | + controls.update(); |
182 | 249 |
|
183 | 250 | lutPass.intensityNode.value = params.intensity;
|
184 | 251 |
|
|
195 | 262 | }
|
196 | 263 |
|
197 | 264 | </script>
|
198 |
| - |
199 | 265 | </body>
|
200 | 266 | </html>
|
0 commit comments