You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
249 lines
8.3 KiB
249 lines
8.3 KiB
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);
|
|
}
|
|
|
|
|