314 lines
11 KiB
HTML
314 lines
11 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Three.js - Texture Mapping & Filter Comparison</title>
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
overflow: hidden;
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
}
|
|
canvas {
|
|
display: block;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
}
|
|
#info {
|
|
position: absolute;
|
|
top: 10px;
|
|
left: 10px;
|
|
color: white;
|
|
background: rgba(0, 0, 0, 0.7);
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
font-size: 14px;
|
|
max-width: 400px;
|
|
z-index: 100;
|
|
}
|
|
#info h2 {
|
|
margin: 0 0 10px 0;
|
|
font-size: 18px;
|
|
}
|
|
#info .web-source {
|
|
margin-top: 10px;
|
|
padding-top: 10px;
|
|
border-top: 1px solid rgba(255,255,255,0.3);
|
|
font-size: 12px;
|
|
opacity: 0.8;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="info">
|
|
<h2>Texture Filter Comparison</h2>
|
|
<p><strong>Technique:</strong> TextureLoader, minFilter, magFilter comparison</p>
|
|
<p><strong>Learning:</strong> Demonstrates NearestFilter (pixelated) vs LinearFilter (smooth blending), texture wrapping, and UV mapping with procedural textures.</p>
|
|
<p><strong>Features:</strong></p>
|
|
<ul style="margin: 5px 0; padding-left: 20px;">
|
|
<li>Left cube: NearestFilter (crisp pixels)</li>
|
|
<li>Center sphere: LinearFilter (smooth)</li>
|
|
<li>Right torus: Repeating texture with wrapping</li>
|
|
<li>All use procedural canvas textures</li>
|
|
</ul>
|
|
<div class="web-source">
|
|
<strong>Web Source:</strong><br>
|
|
<a href="https://threejs.org/manual/en/textures.html" target="_blank" style="color: #4fc3f7;">https://threejs.org/manual/en/textures.html</a><br>
|
|
<em>Applied: TextureLoader patterns, filter types (NearestFilter vs LinearFilter), texture wrapping (RepeatWrapping), and UV repeat settings</em>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="importmap">
|
|
{
|
|
"imports": {
|
|
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
|
|
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<script type="module">
|
|
import * as THREE from 'three';
|
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
|
|
// Scene setup
|
|
let camera, scene, renderer, controls;
|
|
let cube, sphere, torus;
|
|
|
|
init();
|
|
animate();
|
|
|
|
function init() {
|
|
// Camera setup
|
|
camera = new THREE.PerspectiveCamera(
|
|
75,
|
|
window.innerWidth / window.innerHeight,
|
|
0.1,
|
|
1000
|
|
);
|
|
camera.position.set(0, 2, 8);
|
|
|
|
// Scene
|
|
scene = new THREE.Scene();
|
|
scene.background = new THREE.Color(0x1a1a2e);
|
|
|
|
// Renderer (WebGL)
|
|
renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
renderer.setPixelRatio(window.devicePixelRatio);
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
document.body.appendChild(renderer.domElement);
|
|
|
|
// OrbitControls for interaction
|
|
controls = new OrbitControls(camera, renderer.domElement);
|
|
controls.enableDamping = true;
|
|
controls.dampingFactor = 0.05;
|
|
|
|
// Create visualization using learned texture techniques
|
|
createVisualization();
|
|
|
|
// Lighting
|
|
const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
|
|
scene.add(ambientLight);
|
|
|
|
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
|
directionalLight.position.set(5, 5, 5);
|
|
scene.add(directionalLight);
|
|
|
|
const pointLight = new THREE.PointLight(0x4fc3f7, 0.6);
|
|
pointLight.position.set(-5, 3, -5);
|
|
scene.add(pointLight);
|
|
|
|
// Handle resize
|
|
window.addEventListener('resize', onWindowResize);
|
|
}
|
|
|
|
function createVisualization() {
|
|
// Create procedural textures using canvas
|
|
// This demonstrates texture creation without external image files
|
|
|
|
// Texture 1: Checkerboard pattern for NearestFilter demonstration
|
|
const canvas1 = document.createElement('canvas');
|
|
canvas1.width = 128;
|
|
canvas1.height = 128;
|
|
const ctx1 = canvas1.getContext('2d');
|
|
|
|
// Draw checkerboard
|
|
const tileSize = 16;
|
|
for (let y = 0; y < canvas1.height; y += tileSize) {
|
|
for (let x = 0; x < canvas1.width; x += tileSize) {
|
|
const isEven = ((x / tileSize) + (y / tileSize)) % 2 === 0;
|
|
ctx1.fillStyle = isEven ? '#ff6b6b' : '#4ecdc4';
|
|
ctx1.fillRect(x, y, tileSize, tileSize);
|
|
}
|
|
}
|
|
|
|
const texture1 = new THREE.CanvasTexture(canvas1);
|
|
texture1.colorSpace = THREE.SRGBColorSpace;
|
|
// NearestFilter: No interpolation, shows crisp pixels when magnified
|
|
texture1.magFilter = THREE.NearestFilter;
|
|
texture1.minFilter = THREE.NearestFilter;
|
|
|
|
// Texture 2: Gradient pattern for LinearFilter demonstration
|
|
const canvas2 = document.createElement('canvas');
|
|
canvas2.width = 256;
|
|
canvas2.height = 256;
|
|
const ctx2 = canvas2.getContext('2d');
|
|
|
|
// Create radial gradient
|
|
const gradient = ctx2.createRadialGradient(128, 128, 0, 128, 128, 128);
|
|
gradient.addColorStop(0, '#ffd93d');
|
|
gradient.addColorStop(0.5, '#6bcf7f');
|
|
gradient.addColorStop(1, '#4d96ff');
|
|
ctx2.fillStyle = gradient;
|
|
ctx2.fillRect(0, 0, canvas2.width, canvas2.height);
|
|
|
|
// Add some detail
|
|
ctx2.strokeStyle = 'rgba(255, 255, 255, 0.3)';
|
|
ctx2.lineWidth = 2;
|
|
for (let i = 0; i < 8; i++) {
|
|
ctx2.beginPath();
|
|
ctx2.arc(128, 128, 20 + i * 15, 0, Math.PI * 2);
|
|
ctx2.stroke();
|
|
}
|
|
|
|
const texture2 = new THREE.CanvasTexture(canvas2);
|
|
texture2.colorSpace = THREE.SRGBColorSpace;
|
|
// LinearFilter: Interpolates between pixels, creates smooth appearance
|
|
texture2.magFilter = THREE.LinearFilter;
|
|
texture2.minFilter = THREE.LinearMipmapLinearFilter; // Best quality with mipmaps
|
|
|
|
// Texture 3: Pattern with wrapping and repeating
|
|
const canvas3 = document.createElement('canvas');
|
|
canvas3.width = 64;
|
|
canvas3.height = 64;
|
|
const ctx3 = canvas3.getContext('2d');
|
|
|
|
// Create repeating pattern
|
|
ctx3.fillStyle = '#2d3561';
|
|
ctx3.fillRect(0, 0, 64, 64);
|
|
|
|
ctx3.strokeStyle = '#f72585';
|
|
ctx3.lineWidth = 3;
|
|
ctx3.beginPath();
|
|
ctx3.moveTo(0, 0);
|
|
ctx3.lineTo(64, 64);
|
|
ctx3.moveTo(64, 0);
|
|
ctx3.lineTo(0, 64);
|
|
ctx3.stroke();
|
|
|
|
ctx3.fillStyle = '#7209b7';
|
|
ctx3.beginPath();
|
|
ctx3.arc(32, 32, 15, 0, Math.PI * 2);
|
|
ctx3.fill();
|
|
|
|
const texture3 = new THREE.CanvasTexture(canvas3);
|
|
texture3.colorSpace = THREE.SRGBColorSpace;
|
|
texture3.magFilter = THREE.LinearFilter;
|
|
texture3.minFilter = THREE.LinearMipmapLinearFilter;
|
|
|
|
// Enable texture wrapping for repeating patterns
|
|
texture3.wrapS = THREE.RepeatWrapping;
|
|
texture3.wrapT = THREE.RepeatWrapping;
|
|
// Repeat the texture 3 times in both directions
|
|
texture3.repeat.set(3, 3);
|
|
|
|
// Create three objects with different textures and filters
|
|
|
|
// 1. Cube with NearestFilter (pixelated look)
|
|
const cubeGeometry = new THREE.BoxGeometry(2, 2, 2);
|
|
const cubeMaterial = new THREE.MeshStandardMaterial({
|
|
map: texture1,
|
|
roughness: 0.7,
|
|
metalness: 0.3
|
|
});
|
|
cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
|
|
cube.position.x = -4;
|
|
scene.add(cube);
|
|
|
|
// 2. Sphere with LinearFilter (smooth blending)
|
|
const sphereGeometry = new THREE.SphereGeometry(1.2, 32, 32);
|
|
const sphereMaterial = new THREE.MeshStandardMaterial({
|
|
map: texture2,
|
|
roughness: 0.5,
|
|
metalness: 0.4
|
|
});
|
|
sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
|
|
sphere.position.x = 0;
|
|
scene.add(sphere);
|
|
|
|
// 3. Torus with repeating texture and wrapping
|
|
const torusGeometry = new THREE.TorusGeometry(1.2, 0.5, 16, 50);
|
|
const torusMaterial = new THREE.MeshStandardMaterial({
|
|
map: texture3,
|
|
roughness: 0.6,
|
|
metalness: 0.5
|
|
});
|
|
torus = new THREE.Mesh(torusGeometry, torusMaterial);
|
|
torus.position.x = 4;
|
|
scene.add(torus);
|
|
|
|
// Add labels (optional floating text indicators)
|
|
addLabel('NearestFilter', -4, -2, 0);
|
|
addLabel('LinearFilter', 0, -2, 0);
|
|
addLabel('Repeating Texture', 4, -2, 0);
|
|
}
|
|
|
|
function addLabel(text, x, y, z) {
|
|
// Create a simple text sprite using canvas
|
|
const canvas = document.createElement('canvas');
|
|
const context = canvas.getContext('2d');
|
|
canvas.width = 512;
|
|
canvas.height = 128;
|
|
|
|
context.fillStyle = 'rgba(0, 0, 0, 0.6)';
|
|
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
|
|
context.font = 'Bold 48px Arial';
|
|
context.fillStyle = 'white';
|
|
context.textAlign = 'center';
|
|
context.textBaseline = 'middle';
|
|
context.fillText(text, 256, 64);
|
|
|
|
const texture = new THREE.CanvasTexture(canvas);
|
|
texture.colorSpace = THREE.SRGBColorSpace;
|
|
|
|
const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
|
|
const sprite = new THREE.Sprite(spriteMaterial);
|
|
sprite.position.set(x, y, z);
|
|
sprite.scale.set(2, 0.5, 1);
|
|
scene.add(sprite);
|
|
}
|
|
|
|
function onWindowResize() {
|
|
camera.aspect = window.innerWidth / window.innerHeight;
|
|
camera.updateProjectionMatrix();
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
}
|
|
|
|
function animate() {
|
|
requestAnimationFrame(animate);
|
|
|
|
// Animate objects to show textures from different angles
|
|
const time = Date.now() * 0.001;
|
|
|
|
// Cube rotates to show filter differences clearly
|
|
cube.rotation.x = time * 0.3;
|
|
cube.rotation.y = time * 0.5;
|
|
|
|
// Sphere rotates slowly
|
|
sphere.rotation.y = time * 0.4;
|
|
|
|
// Torus rotates to show wrapping pattern
|
|
torus.rotation.x = time * 0.2;
|
|
torus.rotation.y = time * 0.6;
|
|
|
|
// Update controls
|
|
controls.update();
|
|
|
|
renderer.render(scene, camera);
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|