123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 |
- import * as THREE from 'three';
- import { threejsLessonUtils } from './threejs-lesson-utils.js';
- {
- const loader = new THREE.TextureLoader();
- function loadTextureAndPromise( url ) {
- let textureResolve;
- const promise = new Promise( ( resolve ) => {
- textureResolve = resolve;
- } );
- const texture = loader.load( url, ( texture ) => {
- textureResolve( texture );
- } );
- return {
- texture,
- promise,
- };
- }
- const filterTextureInfo = loadTextureAndPromise( '/manual/resources/images/mip-example.png' );
- const filterTexture = filterTextureInfo.texture;
- const filterTexturePromise = filterTextureInfo.promise;
- function filterCube( scale, texture ) {
- const size = 8;
- const geometry = new THREE.BoxGeometry( size, size, size );
- const material = new THREE.MeshBasicMaterial( {
- map: texture || filterTexture,
- } );
- const mesh = new THREE.Mesh( geometry, material );
- mesh.scale.set( scale, scale, scale );
- return mesh;
- }
- function lowResCube( scale, pixelSize = 16 ) {
- const mesh = filterCube( scale );
- const renderTarget = new THREE.WebGLRenderTarget( 1, 1, {
- magFilter: THREE.NearestFilter,
- minFilter: THREE.NearestFilter,
- } );
- const planeScene = new THREE.Scene();
- const plane = new THREE.PlaneGeometry( 1, 1 );
- const planeMaterial = new THREE.MeshBasicMaterial( {
- map: renderTarget.texture,
- } );
- const planeMesh = new THREE.Mesh( plane, planeMaterial );
- planeScene.add( planeMesh );
- const planeCamera = new THREE.OrthographicCamera( 0, 1, 0, 1, - 1, 1 );
- planeCamera.position.z = 1;
- return {
- obj3D: mesh,
- update( time, renderInfo ) {
- const { width, height, scene, camera, renderer, pixelRatio } = renderInfo;
- const rtWidth = Math.ceil( width / pixelRatio / pixelSize );
- const rtHeight = Math.ceil( height / pixelRatio / pixelSize );
- renderTarget.setSize( rtWidth, rtHeight );
- camera.aspect = rtWidth / rtHeight;
- camera.updateProjectionMatrix();
- renderer.setRenderTarget( renderTarget );
- renderer.render( scene, camera );
- renderer.setRenderTarget( null );
- },
- render( renderInfo ) {
- const { width, height, renderer, pixelRatio } = renderInfo;
- const viewWidth = width / pixelRatio / pixelSize;
- const viewHeight = height / pixelRatio / pixelSize;
- planeCamera.left = - viewWidth / 2;
- planeCamera.right = viewWidth / 2;
- planeCamera.top = viewHeight / 2;
- planeCamera.bottom = - viewHeight / 2;
- planeCamera.updateProjectionMatrix();
- // compute the difference between our renderTarget size
- // and the view size. The renderTarget is a multiple pixels magnified pixels
- // so for example if the view is 15 pixels wide and the magnified pixel size is 10
- // the renderTarget will be 20 pixels wide. We only want to display 15 of those 20
- // pixels so
- planeMesh.scale.set( renderTarget.width, renderTarget.height, 1 );
- renderer.render( planeScene, planeCamera );
- },
- };
- }
- function createMip( level, numLevels, scale ) {
- const u = level / numLevels;
- const size = 2 ** ( numLevels - level - 1 );
- const halfSize = Math.ceil( size / 2 );
- const ctx = document.createElement( 'canvas' ).getContext( '2d' );
- ctx.canvas.width = size * scale;
- ctx.canvas.height = size * scale;
- ctx.scale( scale, scale );
- ctx.fillStyle = `hsl(${180 + u * 360 | 0},100%,20%)`;
- ctx.fillRect( 0, 0, size, size );
- ctx.fillStyle = `hsl(${u * 360 | 0},100%,50%)`;
- ctx.fillRect( 0, 0, halfSize, halfSize );
- ctx.fillRect( halfSize, halfSize, halfSize, halfSize );
- return ctx.canvas;
- }
- threejsLessonUtils.init( {
- threejsOptions: { antialias: false },
- } );
- threejsLessonUtils.addDiagrams( {
- filterCube: {
- create() {
- return filterCube( 1 );
- },
- },
- filterCubeSmall: {
- create( info ) {
- return lowResCube( .1, info.renderInfo.pixelRatio );
- },
- },
- filterCubeSmallLowRes: {
- create() {
- return lowResCube( 1 );
- },
- },
- filterCubeMagNearest: {
- async create() {
- const texture = await filterTexturePromise;
- const newTexture = texture.clone();
- newTexture.magFilter = THREE.NearestFilter;
- newTexture.needsUpdate = true;
- return filterCube( 1, newTexture );
- },
- },
- filterCubeMagLinear: {
- async create() {
- const texture = await filterTexturePromise;
- const newTexture = texture.clone();
- newTexture.magFilter = THREE.LinearFilter;
- newTexture.needsUpdate = true;
- return filterCube( 1, newTexture );
- },
- },
- filterModes: {
- async create( props ) {
- const { scene, camera, renderInfo } = props;
- scene.background = new THREE.Color( 'black' );
- camera.far = 150;
- const texture = await filterTexturePromise;
- const root = new THREE.Object3D();
- const depth = 50;
- const plane = new THREE.PlaneGeometry( 1, depth );
- const mipmap = [];
- const numMips = 7;
- for ( let i = 0; i < numMips; ++ i ) {
- mipmap.push( createMip( i, numMips, 1 ) );
- }
- // Is this a design flaw in three.js?
- // AFAIK there's no way to clone a texture really
- // Textures can share an image and I guess deep down
- // if the image is the same they might share a WebGLTexture
- // but no checks for mipmaps I'm guessing. It seems like
- // they shouldn't be checking for same image, the should be
- // checking for same WebGLTexture. Given there is more than
- // WebGL to support maybe they need to abtract WebGLTexture to
- // PlatformTexture or something?
- const meshInfos = [
- { x: - 1, y: 1, minFilter: THREE.NearestFilter, magFilter: THREE.NearestFilter },
- { x: 0, y: 1, minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter },
- { x: 1, y: 1, minFilter: THREE.NearestMipmapNearestFilter, magFilter: THREE.LinearFilter },
- { x: - 1, y: - 1, minFilter: THREE.NearestMipmapLinearFilter, magFilter: THREE.LinearFilter },
- { x: 0, y: - 1, minFilter: THREE.LinearMipmapNearestFilter, magFilter: THREE.LinearFilter },
- { x: 1, y: - 1, minFilter: THREE.LinearMipmapLinearFilter, magFilter: THREE.LinearFilter },
- ].map( ( info ) => {
- const copyTexture = texture.clone();
- copyTexture.minFilter = info.minFilter;
- copyTexture.magFilter = info.magFilter;
- copyTexture.wrapT = THREE.RepeatWrapping;
- copyTexture.repeat.y = depth;
- copyTexture.needsUpdate = true;
- const mipTexture = new THREE.CanvasTexture( mipmap[ 0 ] );
- mipTexture.mipmaps = mipmap;
- mipTexture.minFilter = info.minFilter;
- mipTexture.magFilter = info.magFilter;
- mipTexture.wrapT = THREE.RepeatWrapping;
- mipTexture.repeat.y = depth;
- const material = new THREE.MeshBasicMaterial( {
- map: copyTexture,
- } );
- const mesh = new THREE.Mesh( plane, material );
- mesh.rotation.x = Math.PI * .5 * info.y;
- mesh.position.x = info.x * 1.5;
- mesh.position.y = info.y;
- root.add( mesh );
- return {
- material,
- copyTexture,
- mipTexture,
- };
- } );
- scene.add( root );
- renderInfo.elem.addEventListener( 'click', () => {
- for ( const meshInfo of meshInfos ) {
- const { material, copyTexture, mipTexture } = meshInfo;
- material.map = material.map === copyTexture ? mipTexture : copyTexture;
- }
- } );
- return {
- update( time, renderInfo ) {
- const { camera } = renderInfo;
- camera.position.y = Math.sin( time * .2 ) * .5;
- },
- trackball: false,
- };
- },
- },
- } );
- const textureDiagrams = {
- differentColoredMips( parent ) {
- const numMips = 7;
- for ( let i = 0; i < numMips; ++ i ) {
- const elem = createMip( i, numMips, 4 );
- elem.className = 'border';
- elem.style.margin = '1px';
- parent.appendChild( elem );
- }
- },
- };
- function createTextureDiagram( elem ) {
- const name = elem.dataset.textureDiagram;
- const info = textureDiagrams[ name ];
- info( elem );
- }
- [ ...document.querySelectorAll( '[data-texture-diagram]' ) ].forEach( createTextureDiagram );
- }
|