123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- 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);
- }
|