|
@@ -35,7 +35,7 @@
|
|
|
<script type="module">
|
|
<script type="module">
|
|
|
|
|
|
|
|
import * as THREE from 'three/webgpu';
|
|
import * as THREE from 'three/webgpu';
|
|
|
- import { pass, mix, mul, oneMinus, positionLocal, smoothstep, texture, time, rotateUV, Fn, uv, vec2, vec3, vec4, uniform, posterize, floor, float, sin, fract, dot, step, color, normalWorld, length, atan, replaceDefaultUV } from 'three/tsl';
|
|
|
|
|
|
|
+ import { pass, mix, mul, oneMinus, positionLocal, smoothstep, texture, time, rotateUV, Fn, uv, vec2, vec3, vec4, uniform, posterize, floor, float, sin, fract, dot, step, color, normalWorld, length, atan, replaceDefaultUV, screenSize } from 'three/tsl';
|
|
|
import { retroPass } from 'three/addons/tsl/display/RetroPassNode.js';
|
|
import { retroPass } from 'three/addons/tsl/display/RetroPassNode.js';
|
|
|
import { bayerDither } from 'three/addons/tsl/math/Bayer.js';
|
|
import { bayerDither } from 'three/addons/tsl/math/Bayer.js';
|
|
|
import { scanlines, vignette, colorBleeding, barrelUV } from 'three/addons/tsl/display/CRT.js';
|
|
import { scanlines, vignette, colorBleeding, barrelUV } from 'three/addons/tsl/display/CRT.js';
|
|
@@ -43,10 +43,13 @@
|
|
|
|
|
|
|
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
|
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
|
|
|
+ import { HDRLoader } from 'three/addons/loaders/HDRLoader.js';
|
|
|
|
|
|
|
|
import { Inspector } from 'three/addons/inspector/Inspector.js';
|
|
import { Inspector } from 'three/addons/inspector/Inspector.js';
|
|
|
|
|
|
|
|
let camera, scene, renderer, renderPipeline, controls;
|
|
let camera, scene, renderer, renderPipeline, controls;
|
|
|
|
|
+ let environment;
|
|
|
|
|
+ let currentModel;
|
|
|
|
|
|
|
|
init();
|
|
init();
|
|
|
|
|
|
|
@@ -136,17 +139,90 @@
|
|
|
const gltfLoader = new GLTFLoader();
|
|
const gltfLoader = new GLTFLoader();
|
|
|
const textureLoader = new THREE.TextureLoader();
|
|
const textureLoader = new THREE.TextureLoader();
|
|
|
|
|
|
|
|
- // baked model
|
|
|
|
|
|
|
+ // Model
|
|
|
|
|
|
|
|
- gltfLoader.load(
|
|
|
|
|
- './models/gltf/coffeeMug.glb',
|
|
|
|
|
- ( gltf ) => {
|
|
|
|
|
|
|
+ const models = {
|
|
|
|
|
+ 'Coffee Mug': 'models/gltf/coffeeMug.glb',
|
|
|
|
|
+ 'Damaged Helmet': 'models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf',
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- gltf.scene.getObjectByName( 'baked' ).material.map.anisotropy = 8;
|
|
|
|
|
- scene.add( gltf.scene );
|
|
|
|
|
|
|
+ function loadModel( name ) {
|
|
|
|
|
+
|
|
|
|
|
+ function loadEnvironment() {
|
|
|
|
|
+
|
|
|
|
|
+ if ( environment ) return;
|
|
|
|
|
+
|
|
|
|
|
+ environment = new HDRLoader()
|
|
|
|
|
+ .setPath( 'textures/equirectangular/' )
|
|
|
|
|
+ .load( 'venice_sunset_1k.hdr', ( texture ) => {
|
|
|
|
|
+
|
|
|
|
|
+ texture.mapping = THREE.EquirectangularReflectionMapping;
|
|
|
|
|
+ scene.environment = texture;
|
|
|
|
|
+
|
|
|
|
|
+ // re-invalidate retro pass textures
|
|
|
|
|
+
|
|
|
|
|
+ retro.dispose();
|
|
|
|
|
+
|
|
|
|
|
+ } );
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
- );
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if ( currentModel ) {
|
|
|
|
|
+
|
|
|
|
|
+ scene.remove( currentModel );
|
|
|
|
|
+ currentModel.traverse( ( child ) => {
|
|
|
|
|
+
|
|
|
|
|
+ if ( child.isMesh ) {
|
|
|
|
|
+
|
|
|
|
|
+ child.geometry.dispose();
|
|
|
|
|
+ child.material.dispose();
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ gltfLoader.load( models[ name ], ( gltf ) => {
|
|
|
|
|
+
|
|
|
|
|
+ currentModel = gltf.scene;
|
|
|
|
|
+ currentModel.position.set( 0, 0, 0 );
|
|
|
|
|
+
|
|
|
|
|
+ smoke.visible = false;
|
|
|
|
|
+
|
|
|
|
|
+ if ( name === 'Damaged Helmet' ) {
|
|
|
|
|
+
|
|
|
|
|
+ loadEnvironment();
|
|
|
|
|
+
|
|
|
|
|
+ currentModel.scale.setScalar( 3 );
|
|
|
|
|
+ currentModel.position.y = 1;
|
|
|
|
|
+
|
|
|
|
|
+ } else if ( name === 'Coffee Mug' ) {
|
|
|
|
|
+
|
|
|
|
|
+ smoke.visible = true;
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ scene.add( currentModel );
|
|
|
|
|
+
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ loadModel( 'Coffee Mug' );
|
|
|
|
|
+
|
|
|
|
|
+ // lighting
|
|
|
|
|
+
|
|
|
|
|
+ const ambientLight = new THREE.AmbientLight( 0x404040, 2 );
|
|
|
|
|
+ scene.add( ambientLight );
|
|
|
|
|
+
|
|
|
|
|
+ const directionalLight = new THREE.DirectionalLight( 0xffffff, 3 );
|
|
|
|
|
+ directionalLight.position.set( 5, 10, 5 );
|
|
|
|
|
+ scene.add( directionalLight );
|
|
|
|
|
+
|
|
|
|
|
+ const pointLight = new THREE.PointLight( 0xff6600, 5, 20 );
|
|
|
|
|
+ pointLight.position.set( - 3, 3, 2 );
|
|
|
|
|
+ scene.add( pointLight );
|
|
|
|
|
|
|
|
// geometry
|
|
// geometry
|
|
|
|
|
|
|
@@ -235,9 +311,9 @@
|
|
|
const colorDepthSteps = uniform( 32 );
|
|
const colorDepthSteps = uniform( 32 );
|
|
|
|
|
|
|
|
// CRT effect parameters (subtle for PS1 look)
|
|
// CRT effect parameters (subtle for PS1 look)
|
|
|
- const scanlineIntensity = uniform( 0.08 ); // subtle scanlines
|
|
|
|
|
- const scanlineCount = uniform( 60 ); // match vertical resolution
|
|
|
|
|
- const scanlineSpeed = uniform( 0.05 ); // slow scroll
|
|
|
|
|
|
|
+ const scanlineIntensity = uniform( 0.3 ); // subtle scanlines
|
|
|
|
|
+ const scanlineDensity = uniform( 1 ); // 0.1-1: normalized scanline density (1 = full screen resolution)
|
|
|
|
|
+ const scanlineSpeed = uniform( 0.0 ); // no scanline movement
|
|
|
const vignetteIntensity = uniform( 0.3 ); // subtle vignette
|
|
const vignetteIntensity = uniform( 0.3 ); // subtle vignette
|
|
|
const bleeding = uniform( 0.001 ); // minimal bleeding
|
|
const bleeding = uniform( 0.001 ); // minimal bleeding
|
|
|
const curvature = uniform( 0.02 ); // subtle curve
|
|
const curvature = uniform( 0.02 ); // subtle curve
|
|
@@ -252,13 +328,15 @@
|
|
|
const distortedUV = barrelUV( curvature );
|
|
const distortedUV = barrelUV( curvature );
|
|
|
const distortedDelta = circle( curvature.add( .1 ).mul( 10 ), 1 ).mul( curvature ).mul( .05 );
|
|
const distortedDelta = circle( curvature.add( .1 ).mul( 10 ), 1 ).mul( curvature ).mul( .05 );
|
|
|
|
|
|
|
|
- let retroPipeline = retroPass( scene, camera, { affineDistortion } );
|
|
|
|
|
|
|
+ const retro = retroPass( scene, camera, { affineDistortion } );
|
|
|
|
|
+
|
|
|
|
|
+ let retroPipeline = retro;
|
|
|
retroPipeline = replaceDefaultUV( distortedUV, retroPipeline );
|
|
retroPipeline = replaceDefaultUV( distortedUV, retroPipeline );
|
|
|
retroPipeline = colorBleeding( retroPipeline, bleeding.add( distortedDelta ) );
|
|
retroPipeline = colorBleeding( retroPipeline, bleeding.add( distortedDelta ) );
|
|
|
retroPipeline = bayerDither( retroPipeline, colorDepthSteps );
|
|
retroPipeline = bayerDither( retroPipeline, colorDepthSteps );
|
|
|
retroPipeline = posterize( retroPipeline, colorDepthSteps );
|
|
retroPipeline = posterize( retroPipeline, colorDepthSteps );
|
|
|
retroPipeline = vignette( retroPipeline, vignetteIntensity, 0.6 );
|
|
retroPipeline = vignette( retroPipeline, vignetteIntensity, 0.6 );
|
|
|
- retroPipeline = scanlines( retroPipeline, scanlineIntensity, scanlineCount, scanlineSpeed );
|
|
|
|
|
|
|
+ retroPipeline = scanlines( retroPipeline, scanlineIntensity, screenSize.y.mul( scanlineDensity ), scanlineSpeed );
|
|
|
|
|
|
|
|
renderPipeline.outputNode = retroPipeline;
|
|
renderPipeline.outputNode = retroPipeline;
|
|
|
|
|
|
|
@@ -278,6 +356,8 @@
|
|
|
|
|
|
|
|
const gui = renderer.inspector.createParameters( 'Settings' );
|
|
const gui = renderer.inspector.createParameters( 'Settings' );
|
|
|
|
|
|
|
|
|
|
+ gui.add( { model: 'Coffee Mug' }, 'model', Object.keys( models ) ).name( 'Model' ).onChange( loadModel );
|
|
|
|
|
+
|
|
|
gui.add( { enabled: true }, 'enabled' ).onChange( v => {
|
|
gui.add( { enabled: true }, 'enabled' ).onChange( v => {
|
|
|
|
|
|
|
|
renderPipeline.outputNode = v ? retroPipeline : defaultPass;
|
|
renderPipeline.outputNode = v ? retroPipeline : defaultPass;
|
|
@@ -288,11 +368,16 @@
|
|
|
gui.add( curvature, 'value', 0, 0.2, 0.01 ).name( 'Curvature' );
|
|
gui.add( curvature, 'value', 0, 0.2, 0.01 ).name( 'Curvature' );
|
|
|
gui.add( colorDepthSteps, 'value', 4, 32, 1 ).name( 'Color Depth' );
|
|
gui.add( colorDepthSteps, 'value', 4, 32, 1 ).name( 'Color Depth' );
|
|
|
gui.add( scanlineIntensity, 'value', 0, 1, 0.01 ).name( 'Scanlines' );
|
|
gui.add( scanlineIntensity, 'value', 0, 1, 0.01 ).name( 'Scanlines' );
|
|
|
- gui.add( scanlineCount, 'value', 20, 480, 1 ).name( 'Scanline Count' );
|
|
|
|
|
|
|
+ gui.add( scanlineDensity, 'value', 0.02, 1, 0.01 ).name( 'Scanline Density' );
|
|
|
gui.add( scanlineSpeed, 'value', 0, .1, 0.01 ).name( 'Scanline Speed' );
|
|
gui.add( scanlineSpeed, 'value', 0, .1, 0.01 ).name( 'Scanline Speed' );
|
|
|
gui.add( vignetteIntensity, 'value', 0, 1, 0.01 ).name( 'Vignette' );
|
|
gui.add( vignetteIntensity, 'value', 0, 1, 0.01 ).name( 'Vignette' );
|
|
|
gui.add( bleeding, 'value', 0, 0.005, 0.001 ).name( 'Color Bleeding' );
|
|
gui.add( bleeding, 'value', 0, 0.005, 0.001 ).name( 'Color Bleeding' );
|
|
|
gui.add( affineDistortion, 'value', 0, 1 ).name( 'Affine Distortion' );
|
|
gui.add( affineDistortion, 'value', 0, 1 ).name( 'Affine Distortion' );
|
|
|
|
|
+ gui.add( retro, 'filterTextures' ).name( 'Filter Textures' ).onChange( () => {
|
|
|
|
|
+
|
|
|
|
|
+ retro.dispose();
|
|
|
|
|
+
|
|
|
|
|
+ } );
|
|
|
|
|
|
|
|
window.addEventListener( 'resize', onWindowResize );
|
|
window.addEventListener( 'resize', onWindowResize );
|
|
|
|
|
|