====== Quick Start ====== * link code: (ref: https://threejs.org/docs/index.html#manual/en/introduction/Installation ) * * resource: * https://threejsfundamentals.org/threejs/lessons/threejs-fundamentals.html * https://codepen.io/rachsmith/post/beginning-with-3d-webgl-pt-1-the-scene * https://www.august.com.au/blog/animating-scenes-with-webgl-three-js/ * https://sbcode.net/threejs/dat-gui/ * https://github.com/markuslerner/THREE.Interactive * video: * https://threejs-journey.xyz/ * example * image plane fly-through: https://www.oculus.com/medal-of-honor/ * 3d pin ball: http://letsplay.ouigo.com/ * world switch: https://zen.ly/ * environment: https://chartogne-taillet.com/en * https://www.facebook.com/groups/webtrendcollection * wave: * https://1stwebdesigner.com/examples-of-three-js-in-action/ ====== Browse Support ====== * some feature using WebGL 2.0 and some old browser or some mobile browser may not support it, * you can use code to detect webgl2 support and dynamic change the way of coding to support other browser * webgl2 support browser tabel: https://caniuse.com/webgl2 * check webgl2 support // check webgl2 support for multi sample version const gl = document.createElement('canvas').getContext('webgl2'); if (!gl) { alert('your browser/OS/drivers do not support WebGL2'); console.log('your browser/OS/drivers do not support WebGL2'); } else { console.log('webgl2 works!'); } ====== Tools ====== * node.js (other downloads for zip version): https://nodejs.org/en/download/ * gui js options: * dat.GUI * control-panel * ControlKit * Guify * Oui * https://tinypng.com/ * https://matheowis.github.io/HDRI-to-CubeMap/ * prepare tip: * use 2x1 ratio panorama with horizontal tile-able texture as base layer * photoshop > spherical panorama > new panorama layer from selected layer to create a sphere texture * paint the seam on pole area, (since already fix horizontal seam in 2D) * once done, spherical panorama > export the result as 2x1 texture, then use above panorama to cubemap tool * https://gero3.github.io/facetype.js/ * texture * https://www.poliigon.com/ * https://3dtextures.me/ * https://www.arroway-textures.ch/ * https://polyhaven.com/ * https://github.com/nidorx/matcaps * https://kenney.nl/assets/particle-pack ====== Three.js Concept ====== * Object3D (position, rotation, scale, quaternion rotation): Mesh, Light, Camera, Group, AxesHelper * Mesh = Geometry + Material/Shader * HTML Element Content <=(Result Target) Renderer.render <= Mesh + Light + Camera **example's js vs jsm** * jsm : js module, it is for use with three.js as module style, * like in code "import { OrbitControls } from './path/OrbitControls.js" * and use its class like "new OrbitControls( main_cam, main_render.domElement );" * js: the traditional js way, * load under THREE like "" * and use its class like "new THREE.OrbitControls( main_cam, main_render.domElement );" **3D Format for Web** * GLTF: GL transmission format, support geo/mat/cam/lgt/scene/ani/bone/morph, (json, binary, binary compressed, embed texture) * gltf_obj.scene.children (array) > [] > cam/mesh/light ====== Common Code ====== * matcap material (fake light material without light) const brown_mctex = main_texLoader.load('./img/matcap/746761_291C19_AB9385_3C2B27.jpg') const brown_mat = new THREE.MeshMatcapMaterial({matcap:brown_mctex}) * color material const white_mat = new THREE.MeshStandardMaterial({ color:'#54c1c8', roughness: 0.3, metalness: 0.2, side: THREE.DoubleSide }) * helper indicator // axis const main_axis = new THREE.AxesHelper() scene.add(main_axis) **Events** * mouse tracking //# == add event: mouse position tracking let mouse = { x: 0, y: 0 } window.addEventListener('mousemove', (event)=>{ //console.log(event.clientX) // from top left corner // top/left to center coordinate mouse.x = (event.clientX / output_size.width)*2-1 mouse.y = -(event.clientY / output_size.height)*2+1 }) * double click full window window.addEventListener('dblclick', ()=>{ const full_screen_element = document.fullscreenElement || document.webkitFullscreenElement //output.requestFullScreen() if (!full_screen_element){ if (output.requestFullscreen){ // normal browse support output.requestFullscreen() } else if (output.webkitRequestFullscreen){ // safari output.webkitRequestFullscreen() } }else{ if (document.exitFullscreen){ // normal browse support document.exitFullscreen() } else if (document.webkitExitFullscreen){ // safari document.webkitExitFullscreen() } } }) * simple key down event const xSpeed = 0.1; const ySpeed = 0.1; window.addEventListener("keydown", onDocumentKeyDown, false); function onDocumentKeyDown(event) { const keyCode = event.which; console.log(keyCode) if (keyCode == 40) { boat_obj.position.z += ySpeed; } else if (keyCode == 38) { boat_obj.position.z -= ySpeed; } else if (keyCode == 37) { boat_obj.position.x -= xSpeed; } else if (keyCode == 39) { console.log('forward') boat_obj.position.x += xSpeed; } else if (keyCode == 32) { boat_obj.position.set(0, 0, 0); } }; * extra offset key event for tick() const offset_step = 0.02 if ( keyboard.pressed("w") ){ main_boat_offset.position.y -= offset_step console.log(main_boat_offset.position.y) } if ( keyboard.pressed("s") ){ main_boat_offset.position.y += offset_step console.log(main_boat_offset.position.y) } if ( keyboard.pressed("e") ){ main_boat_offset.position.z -= offset_step console.log(main_boat_offset.position.z) } if ( keyboard.pressed("q") ){ main_boat_offset.position.z += offset_step console.log(main_boat_offset.position.z) } if ( keyboard.pressed("d") ){ main_boat_offset.position.x -= offset_step console.log(main_boat_offset.position.x) } if ( keyboard.pressed("a") ){ main_boat_offset.position.x += offset_step console.log(main_boat_offset.position.x) } **GUI** * simple gui control // -- gui const gui = new dat.GUI({ closed:true, width: 300}) dat.GUI.toggleHide(); const global_ctrl = { boat: { x: 0, y: 0, z: 0, color:0x00ff00, slide: ()=>{ let tl = gsap.timeline(); const cur_x = green_box_mesh.position.x tl.to(green_box_mesh.position, {duration: 2, x: cur_x+2}) .to(green_box_mesh.position, {duration: 1, x: cur_x}) }, } } gui.add(global_ctrl.boat, 'x', -10, 10, 0.1).name('posX').onChange( ()=> { boat_obj.position.x = global_ctrl.boat.x } ) gui.add(global_ctrl.boat, 'y', -10, 10, 0.1).name('posY').onChange( ()=> { boat_obj.position.y = global_ctrl.boat.y } ) gui.add(global_ctrl.boat, 'z', -50, 50, 0.1).name('posZ').onChange( ()=> { boat_obj.position.z = global_ctrl.boat.z } ) gui.addColor(global_ctrl.boat, 'color').onChange( ()=>{ green_box_mesh.material.color.set(global_ctrl.green_box.color) } ) gui.add(global_ctrl.boat, 'slide') **font** * text //-- font const text_mat = new THREE.MeshMatcapMaterial({matcap:brown_mctex}) let text_mesh = undefined const main_fontLoader = new THREE.FontLoader(load_manager) main_fontLoader.load( './font/helvetiker_regular.typeface.json', (result_font)=>{ console.log('font loaded') const bevel_a = 0.01 //2 const bevel_b = 0.01 //3 const text_geo = new THREE.TextBufferGeometry( 'Drive Through to Next Scene > ',{ font: result_font, size: 0.2, height: .05, curveSegments: 3, // bevelEnabled: true, // bevelThickness: bevel_b, // bevelSize: bevel_a, // bevelOffset: 0, // bevelSegments: 2 } ) text_geo.computeBoundingBox() // max3, min3 text_geo.center() text_mesh = new THREE.Mesh(text_geo, white_2_mat) // text_mat text_mesh.position.y = 1 text_mesh.position.x = 123 scene.add(text_mesh) } ) ====== Three.js Problem and Solution====== * limit orbit pan: https://discourse.threejs.org/t/how-to-limit-pan-in-orbitcontrols-for-orthographiccamera/9061 * limit the maximum view angle on width or basically auto adjust vFOV given hFOV (basically check width and fov dynamically): // ref: https://discourse.threejs.org/t/responsive-renderer-with-limits/4401/16 renderer.setSize( window.innerWidth, window.innerHeight ); camera.aspect = window.innerWidth / window.innerHeight const viewAspect = viewWidth / viewHeight const special = camera.aspect > viewAspect uniform.value = special ? 1 : 0 if(special){ const camH = Math.tan(THREE.Math.degToRad(myFov/2)) const ratio = camera.aspect / viewAspect const newH = camH / ratio const newFov = THREE.Math.radToDeg(Math.atan(newH)) * 2 camera.fov = newFov } else { camera.fov = myFov } camera.updateProjectionMatrix() //https://github.com/mrdoob/three.js/issues/15968 const hFOV = 50; // desired horizontal fov, in degrees camera.fov = Math.atan( Math.tan( hFOV * Math.PI / 360 ) / camera.aspect ) * 360 / Math.PI; // degrees camera.updateProjectionMatrix(); * Handle cloud like soft transparent texture geo properly const tmp_map = child.material.map const tmp_mat = new THREE.MeshBasicMaterial({ map: child.material.map, side: THREE.DoubleSide, transparent: true }) child.material = tmp_mat * Handle leaf like transparent texture geo properly: * ref: https://github.com/KhronosGroup/glTF-Blender-IO/issues/1264 * code // reuse use the png texture, create basic material replace with blend mode to alpha clip and clip at 0.5 const tmp_map = child.material.map const tmp_mat = new THREE.MeshBasicMaterial({ map: child.material.map, side: THREE.DoubleSide, alphaMode:"MASK", alphaTest:0.5 }) child.material = tmp_mat // or set properly in blender before bring into threejs // ref: https://www.youtube.com/watch?v=03isI0_FGLU&t=61s inside blender, transparent node->1-> mix shader use map-c> diffuse node ->2-> mix shader use map-a> mix shader.fac mix shader -> out.surface then side panel: make material option : * blend mode: alpha clip * and set roughness to 1 * set texture node filter and optionally disable mip map use child.material.map.inFilter = THREE.NearestFilter child.material.map.generateMipmaps = false * make object ignore camera frustum culling, so that when camera move closer, object won't disappear if(child.name.startsWith("sch_word_")){ child.frustumCulled = false } * video as texture // ref: https://stemkoski.github.io/Three.js/ // https://threejs.org/docs/#api/en/textures/VideoTexture // 1. define global variable for controls let video = undefined let videoImage = undefined let videoImageContext = undefined let videoTexture = undefined let mov_mat = undefined // 2. (after all your model loaded event) create video material video = document.createElement( 'video' ); video.src = "video/my_video.mp4"; video.load(); // video.play(); // (don't play on start) videoImage = document.createElement( 'canvas' ); videoImage.width = 640; videoImage.height = 480; videoImageContext = videoImage.getContext( '2d' ); // background color if no video present videoImageContext.fillStyle = '#000000'; videoImageContext.fillRect( 0, 0, videoImage.width, videoImage.height ); videoTexture = new THREE.Texture( videoImage ); videoTexture.minFilter = THREE.LinearFilter; videoTexture.magFilter = THREE.LinearFilter; mov_mat = new THREE.MeshBasicMaterial( { map: videoTexture, side:THREE.DoubleSide } ); // overdraw: true, // 3. assign to geo my_video_plane_geo.material = mov_mat; // 4. call video_update() during render each frame function video_update(){ if ( video.readyState === video.HAVE_ENOUGH_DATA ) { videoImageContext.drawImage( video, 0, 0 ); if ( videoTexture ) videoTexture.needsUpdate = true; } } // 5. support functions for call from other event function video_start(){ //bgm_sound.pause(); // option to mute bgm sound if you have any video.currentTime = 0; video.play(); } function video_stop(){ //bgm_sound.play(); // option to on bgm sound if you have any video.pause(); video.currentTime = 0; } * language based texture switch // method 1: (works in firefox, not in chrome) if(child.name == "lang_texture_geo") ){ lang_texture_geo = child if(main_lang=="cn"){ lang_texture_geo.material.map.image.src = "./img/cn_texture.jpg"; } } // method 2: (works in both firefox and chrome) const main_texLoader = new THREE.TextureLoader(load_manager) const cn_text_ctex = main_texLoader.load("./img/cn_texture.jpg"); cn_text_ctex.flipY = false; if(child.name == "lang_texture_geo") ){ lang_texture_geo = child if(main_lang == "cn"){ child.material.map = cn_text_ctex; } } ====== Template ======

Good Animation

Good One

====== template 2 ====== Three
1
Some Description Here
//import './style.css' //import * as THREE from 'three' //import { OrbitControls } from './script/OrbitControls.js' console.log('Hello Three.js') //console.log(THREE) const scene = new THREE.Scene() //-- loading manager const load_ui = document.querySelector('.loadbar') const load_manager = new THREE.LoadingManager( // loaded () => { gsap.delayedCall(0.5, () =>{ console.log('loaded') gsap.to(cover_mesh.material.uniforms.uAlpha, {duration: 3, value: 0 }) load_ui.classList.add('ended') load_ui.style.transform = '' }) }, // progress (itemUrl, itemLoaded, itemTotal) => { console.log('loading') const progress_ratio = itemLoaded / itemTotal load_ui.style.transform = `scaleX(${progress_ratio})` } ) // -- cover-object // original 3d space = // gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0) const cover_mesh = new THREE.Mesh( new THREE.PlaneBufferGeometry(2,2,1,1), new THREE.ShaderMaterial({ // wireframe: true, transparent: true, uniforms:{ uAlpha:{value:1} }, vertexShader:` void main(){ gl_Position = vec4(position, 1.0); } `, fragmentShader: ` uniform float uAlpha; void main(){ gl_FragColor = vec4(0,0,0, uAlpha); } ` }) ) scene.add(cover_mesh) //--points const points = [ { position: new THREE.Vector3(1.55, 0.3, -0.6), element: document.querySelector('.point-0') } ] // Mesh = Geo + Shader const box_geo = new THREE.BoxGeometry(1,2,1) // -- mat (0xff0000, 'red', '#ff0000', 'rgb(255,0,0)', 'rgb(100%,0%,0%)',) // -- 'hsl(0,100%,50%)', 1,0,0 const default_mat = new THREE.MeshBasicMaterial( {color: '#ff0000'} ) // -- mesh const box_mesh = new THREE.Mesh(box_geo, default_mat) box_mesh.position.x=2 box_mesh.scale.set(2,1,1) const blue_box_mesh = new THREE.Mesh(new THREE.SphereBufferGeometry(.5,16,16), new THREE.MeshBasicMaterial( {color: '#0000ff'}) ) scene.add(blue_box_mesh) const green_box_mesh = new THREE.Mesh(new THREE.BoxBufferGeometry(1,1,1,2,2,2), new THREE.MeshBasicMaterial( {color: '#00ff00', wireframe: true}) ) scene.add(green_box_mesh) green_box_mesh.position.x = 1 green_box_mesh.position.z = 1 green_box_mesh.rotation.reorder('YXZ') // -- custom geo const pos_array = new Float32Array([ 0,0,0, 0,4,0, 4,0,0 ]) const pos_attr = new THREE.BufferAttribute(pos_array, 3) const pos_geo = new THREE.BufferGeometry() pos_geo.setAttribute('position', pos_attr) const cust_mesh = new THREE.Mesh(pos_geo, new THREE.MeshBasicMaterial( {color: 0x00ff00, wireframe: true}) ) scene.add(cust_mesh) //box_mesh.rotation.reorder('YXZ') // set order first if needed box_mesh.rotation.y=Math.PI*0.5 // pi value radient value //scene.add(box_mesh) // -- texture // ----- step method /* const door_img_file = new Image() const door_img_tex = new THREE.Texture(door_img_file) door_img_file.onload = () => { door_img_tex.needsUpdate = true } door_img_file.src = './img/door/color.jpg' default_mat.map = door_img_tex default_mat.color = undefined */ // ----- simple method //const loading_manager = new THREE.LoadingManager() //loading_manager.onStart = ()=>{} const door_texLoader = new THREE.TextureLoader(load_manager) // optional parent loading_manager const door_ctex = door_texLoader.load('./img/door/color.jpg') const door_atex = door_texLoader.load('./img/door/alpha.jpg') const door_htex = door_texLoader.load('./img/door/height.jpg') const door_ntex = door_texLoader.load('./img/door/normal.jpg') const door_aotex = door_texLoader.load('./img/door/ambientOcclusion.jpg') const door_mtex = door_texLoader.load('./img/door/metalness.jpg') const door_rtex = door_texLoader.load('./img/door/roughness.jpg') const particle_ctex = door_texLoader.load('./img/sprite/2.png') // const brown_mctex = door_texLoader.load('./img/matcap/7B5254_E9DCC7_B19986_C8AC91.jpg') const brown_mctex = door_texLoader.load('./img/matcap/746761_291C19_AB9385_3C2B27.jpg') /* door_ctex.repeat.x = 1.5 door_ctex.repeat.y = 1.5 door_ctex.wrapS = THREE.MirroredRepeatWrapping door_ctex.wrapT = THREE.MirroredRepeatWrapping door_ctex.offset.x = .5 door_ctex.offset.y = .5 door_ctex.center.x = 0.5 // for rotateCenter door_ctex.center.y = 0.5 door_ctex.rotation = Math.PI*.25 */ //door_ctex.generateMipmaps = false // for those blocky style texture //door_ctex.minFilter = THREE.NearestFilter // linearFilter: blur //door_ctex.magFilter = THREE.NearestFilter // NearestFilter: sharp blocky pixel default_mat.map = door_ctex // after intialized default_mat.color = undefined // default_mat.color = new THREE.Color('pink') default_mat.transparent = true // default_mat.opacity = 0.5 door_atex.minFilter = THREE.NearestFilter // fix distance tex blur issue default_mat.alphaMap = door_atex default_mat.side = THREE.DoubleSide //blue_box_mesh.material.flatShading = true // - change to lambert // blue_box_mesh.material = new THREE.MeshLambertMaterial({color:'blue'}) // need light // blue_box_mesh.material = new THREE.MeshPhongMaterial({color:'blue'}) // need light // blue_box_mesh.material = new THREE.MeshToonMaterial({color:'blue'}) // need light, can take gradientMap blue_box_mesh.material = new THREE.MeshStandardMaterial({color:'grey'}) // need light, more PBR map options // -- light const main_light = new THREE.DirectionalLight(0xffffff, 1) main_light.position.set(1,1,1) scene.add(main_light) // ----- environment method const env_cubeTexLoader = new THREE.CubeTextureLoader(load_manager) const env_tex = env_cubeTexLoader.load([ './img/env/4r/px.png', './img/env/4r/nx.png', './img/env/4r/py.png', './img/env/4r/ny.png', './img/env/4r/pz.png', './img/env/4r/nz.png' ]) scene.background = env_tex scene.environment = env_tex // global mesh envMap setup blue_box_mesh.material.envMap = env_tex blue_box_mesh.material.metalness= .8 blue_box_mesh.material.roughness= .1 const white_mat = new THREE.MeshStandardMaterial({ color:'#eee', roughness: 0.3, metalness: 0.2, side: THREE.DoubleSide }) const base_floor_mesh = new THREE.Mesh(new THREE.PlaneBufferGeometry(5,5,2,2), white_mat) base_floor_mesh.rotation.x = Math.PI*0.5 base_floor_mesh.position.y = -2 scene.add(base_floor_mesh) base_floor_mesh.receiveShadow = true const base_box_mesh = new THREE.Mesh(new THREE.BoxBufferGeometry(0.8,0.8,2,2), white_mat) base_box_mesh.position.y = -1.5 scene.add(base_box_mesh) base_box_mesh.castShadow = true const base_areaLight = new THREE.RectAreaLight(0x4e00ff,5, 1, 1) base_areaLight.position.set(1.5,-1,1.5) base_areaLight.lookAt(base_box_mesh.position) scene.add(base_areaLight) const base_spotLight = new THREE.SpotLight(0x78fff00,0.5, 4, Math.PI*0.25, .25, 1) // 2nd last, softness base_spotLight.position.set(-1.5,-1,1.5) scene.add(base_spotLight) scene.add(base_spotLight.target) base_spotLight.target.position.set(0,-1.5,0) base_spotLight.castShadow = true base_spotLight.shadow.mapSize.width = 1024 base_spotLight.shadow.mapSize.height = 1024 base_spotLight.shadow.radius = 10 // softness // for smooth object, to avoid self shadow casting, you need to tweak per light shadow map //base_spotLight.shadow.normalBias = 0.05 // so push shadow calcultion into geo to avoid geo self shadow // for flat object, try shadow.bias // shadow also can be limited but clipping plane from lgiht const base_spotLight_helper = new THREE.SpotLightHelper(base_spotLight,0xffffff) scene.add(base_spotLight_helper) // -- mesh instance // group const main_grp = new THREE.Group() scene.add(main_grp) main_grp.add(box_mesh) main_grp.position.x=1 //-- font const text_mat = new THREE.MeshMatcapMaterial({matcap:brown_mctex}) text_mat.wireframe = false // text_mat.flatShading = true const main_fontLoader = new THREE.FontLoader(load_manager) main_fontLoader.load( './font/helvetiker_regular.typeface.json', (result_font)=>{ console.log('font loaded') const bevel_a = 0.02 const bevel_b = 0.03 const text_geo = new THREE.TextBufferGeometry( 'Shining & Lucy',{ font: result_font, size: 0.5, height: .05, curveSegments: 3, bevelEnabled: true, bevelThickness: bevel_b, bevelSize: bevel_a, bevelOffset: 0, bevelSegments: 2 } ) text_geo.computeBoundingBox() // max3, min3 text_geo.center() const text_mesh = new THREE.Mesh(text_geo, text_mat) // text_mesh.position.y = -2 scene.add(text_mesh) } ) // -- random geo console.time('randomGeo') for (let i=0; i<100;i++){ const tmp_geo = new THREE.TorusBufferGeometry(0.3,0.2, 20,45) const tmp_mesh = new THREE.Mesh(tmp_geo, text_mat) tmp_mesh.position.x = (Math.random()-0.5)*10 tmp_mesh.position.y = (Math.random()-0.5)*10 tmp_mesh.position.z = (Math.random()-0.5)*10 tmp_mesh.rotation.x = (Math.random()-0.5)*Math.PI tmp_mesh.rotation.y = (Math.random()-0.5)*Math.PI tmp_s = Math.random() tmp_mesh.scale.set(tmp_s, tmp_s, tmp_s) scene.add(tmp_mesh) } console.timeEnd('randomGeo') // camera (x,Y,z) , same coordinate system as Maya, // (x for right, z for close, Y for up) /* const output_size = { width: 800, height: 400 } */ // -- particle const particle_geo = new THREE.SphereBufferGeometry(1,32,32) const particle_mat = new THREE.PointsMaterial({ size: 0.02, sizeAttenuation: true }) const particle_sys = new THREE.Points(particle_geo, particle_mat) scene.add(particle_sys) // - color const color_up = new THREE.Color(0xff0000) const color_dn = new THREE.Color(0x0000ff) console.log(particle_geo.attributes.position.array[1]) const ramp_color_list = new Float32Array(particle_geo.attributes.position.count*3) for (let i = 0; i{ console.log(the_gltf) const boat_obj = the_gltf.scene.children[2] boat_obj.scale.set(0.5,0.5,0.5) boat_obj.position.y= 2 boat_obj.material = new THREE.MeshStandardMaterial({color:'white'}) // this is for per mat env setup, use global scene env instead // boat_obj.material.envMap = env_tex scene.add(boat_obj) // it add to our scene and remove from gltf scene console.log(boat_obj) /* // loop method while(the_gltf.scene.children.length){ scene.add(the_gltf.scene.children[0]) } // copy method const child_list = [...the_gltf.scene.children] for (const child of child_list){ scene.add(child) } // animation, animation is under each node mixer = new THREE.AnimationMixer(the_gltf.scene) const action = mixer.ClipAction(the_gltf.animation[0]) action.play() */ //updateAllMaterial() }) // update all mat const updateAllMaterial = ()=>{ scene.traverse((child)=>{ if (child instanceof THREE.Mesh && child.material instanceof THREE.MeshStandardMaterial){ child.castShadow = true child.receiveShadow = true // child.needsUpdate = true } }) } // full screen const output_size = { width: window.innerWidth, height: window.innerHeight } let ratio = output_size.width / output_size.height const main_cam = new THREE.PerspectiveCamera(75, ratio ) // main_cam.position.z = 5 main_cam.position.set(0,0,5) scene.add(main_cam) // aim Action, like Maya aimConstraint and delete // new THREE.Vector3(0,0,0) main_cam.lookAt(box_mesh.position) // axis const main_axis = new THREE.AxesHelper() scene.add(main_axis) // main_render const output = document.querySelector('canvas.view') const main_render = new THREE.WebGLRenderer( { canvas: output, antialias: true, // need to be setup initially } ) main_render.setSize(output_size.width, output_size.height) main_render.setPixelRatio(Math.min(window.devicePixelRatio, 2) ) // limit to 2x render // physical light style main_render.physicallyCorrectLights = true // abit dimmer // render encoding, default linear main_render.outputEncoding = THREE.sRGBEncoding // tone map (NoToneMapping, LinearToneMapping, ReinhardToneMapping, CineonToneMapping, ACESFilmicToneMapping) // main_render.toneMapping = THREE.ACESFilmicToneMapping // main_render.toneMappingExposure = 3 // - enable shadow globally // -- mesh,light who make shadow needs Enabled (point, direct, spot) // -- mesh receive shadow need enabled // main_render.shadowMap.enabled = true // shadow draw method //(PCFShadowMap, fast:BasicShadowMap, better:PCFSoftShadowMap) // main_render.shadowMap.type = THREE.PCFSoftShadowMap // no shadow blur option // event let mouse = { x: 0, y: 0 } window.addEventListener('mousemove', (event)=>{ //console.log(event.clientX) // from top left corner // top/left to center coordinate mouse.x = (event.clientX / output_size.width)*2-1 mouse.y = -(event.clientY / output_size.height)*2+1 }) window.addEventListener('resize', ()=>{ //console.log("new width:"+window.innerWidth) output_size.width = window.innerWidth output_size.height = window.innerHeight // update cam ratio = output_size.width / output_size.height main_cam.aspect = ratio main_cam.updateProjectionMatrix() // update render main_render.setSize(output_size.width, output_size.height) }) window.addEventListener('dblclick', ()=>{ console.log() const full_screen_element = document.fullscreenElement || document.webkitFullscreenElement //output.requestFullScreen() if (!full_screen_element){ if (output.requestFullscreen){ // normal browse support output.requestFullscreen() } else if (output.webkitRequestFullscreen){ // safari output.webkitRequestFullscreen() } }else{ if (document.exitFullscreen){ // normal browse support document.exitFullscreen() } else if (document.webkitExitFullscreen){ // safari document.webkitExitFullscreen() } } }) // -- gui const gui = new dat.GUI({ closed:true, width: 300}) const global_ctrl = { green_box: { y: 0, color:0x00ff00, slide: ()=>{ let tl = gsap.timeline(); const cur_x = green_box_mesh.position.x tl.to(green_box_mesh.position, {duration: 2, x: cur_x+2}) .to(green_box_mesh.position, {duration: 1, x: cur_x}) }, deleteParticle: ()=>{ particle_random_sys.geometry.dispose() scene.remove(particle_random_sys) } } } gui.add(global_ctrl.green_box, 'y', 0, 5, 0.1).name('posY').onChange( ()=> { green_box_mesh.position.y = global_ctrl.green_box.y } ) gui.addColor(global_ctrl.green_box, 'color').onChange( ()=>{ green_box_mesh.material.color.set(global_ctrl.green_box.color) } ) gui.add(global_ctrl.green_box, 'slide') gui.add(global_ctrl.green_box, 'deleteParticle') gui.add(green_box_mesh, 'visible') gui.add(green_box_mesh.material, 'wireframe') // -- cam ctrl const orbit_ctrl = new THREE.OrbitControls( main_cam, main_render.domElement ); orbit_ctrl.enableDamping = true // enable ease after mouse movement // -- animation gsap gsap.to(blue_box_mesh.position, {duration: 2, delay: 2, x:-4}) gsap.to(blue_box_mesh.position, {duration: 2, delay: 1, x:2}) // -- animation var frame = 1 let start_time = Date.now() // js clock const main_clock = new THREE.Clock() // three clock let pre_time_3 = 0 const tick = () => { if (frame%60 == 0){ //console.log(frame) const cur_time = Date.now() const pass_time = cur_time - start_time //console.log(pass_time/1000) const pass_time_3 = main_clock.getElapsedTime() const delta_time_3 = pass_time_3 - pre_time_3 pre_time_3 = pass_time_3 //console.log('clock:'+pass_time_3) //console.log(mouse) //console.log('pixel: '+window.devicePixelRatio) } frame = frame + 1 // update if (box_mesh.position.x<5){ box_mesh.position.x+=0.01 } box_mesh.rotation.y+=0.01 // update green by mouse green_box_mesh.rotation.y = Math.PI*mouse.x*0.5 green_box_mesh.rotation.x = Math.PI*mouse.y*-0.25 // time orbit_ctrl.update() // light helper update base_spotLight_helper.update() // areaLightHelper more /* base_areaLight_helper.position.copy(base_areaLight.position) base_areaLight_helper.quaternion.copy(base_areaLight.quaternion) base_areaLight_helper.update() */ /* // animation if (mixer!== null){ mixer.update(delta_time_3) } */ // update points for(const point of points){ const screen_pos = point.position.clone() screen_pos.project(main_cam) const tx = screen_pos.x * output_size.width * 0.5 const ty = screen_pos.y * output_size.height * 0.5 * -1 point.element.style.transform = `translate(${tx}px, ${ty}px)` } // re-render main_render.render(scene, main_cam) window.requestAnimationFrame(tick) // loop for next frame } tick() // distanceTo built-in console.log("ObjToCam: "+box_mesh.position.distanceTo(main_cam.position))