Three.js canvas 2 的纹理幂但被裁剪

Three.js canvas texture power of 2 but cropped

在 Three.js 中,最佳做法是将纹理保持为 2 的幂,这实际上是一些较旧的 Chrome 浏览器的要求,例如 32x32、64x64 等

因此我需要创建一个 canvas 二维的纹理,但我需要让 Sprite 可点击,这意味着它需要以某种方式裁剪。

考虑这个例子:

我想保持纹理的尺寸为 2 的幂,但将其裁剪为仅显示白色区域。例如,这样用户就可以将鼠标悬停在标签上。我不想让他们在红色区域徘徊!

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 400;

var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

var config = {
  fontface: 'Arial',
  fontsize: 32,
  fontweight: 500,
  lineheight: 1,
};
var text = 'Hello world!';

function nextPowerOf2(n) {
  return Math.pow(2, Math.ceil(Math.log(n) / Math.log(2)));
}

var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
ctx.font = `${config.fontweight} ${config.fontsize}px/${config.lineheight} ${config.fontface}`;
const textMetrics = ctx.measureText(text);
var textWidth = textMetrics.width;
var textHeight = config.fontsize * config.lineheight;
canvas.width = nextPowerOf2(textWidth);
canvas.height = nextPowerOf2(textHeight);
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, textWidth, textHeight);
ctx.fillStyle = 'black';
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.font = `${config.fontweight} ${config.fontsize}px/${config.lineheight} ${config.fontface}`;
ctx.fillText(text, 0, 0);

console.log('canvas', canvas.width, canvas.height);
console.log('text', textWidth, textHeight);

var texture = new THREE.Texture(canvas);
texture.needsUpdate = true;
var spriteMaterial = new THREE.SpriteMaterial({
  map: texture
});
var sprite = new THREE.Sprite(spriteMaterial);
sprite.scale.set(spriteMaterial.map.image.width, spriteMaterial.map.image.height, 1);
scene.add(sprite);


var animate = function() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
};

animate();
body { margin: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>

这怎么可能?以某种方式裁剪 SpriteMaterial?

您可以设置纹理的重复,以便仅使用的 canvas 部分覆盖 sprite

texture.repeat.set(textWidth / canvas.width, textHeight / canvas.height);

然后你可以将精灵的大小设置为那个部分

sprite.scale.set(textWidth, textHeight, 1);

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 400;

var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

var config = {
  fontface: 'Arial',
  fontsize: 32,
  fontweight: 500,
  lineheight: 1,
};
var text = 'Hello world!';

function nextPowerOf2(n) {
  return Math.pow(2, Math.ceil(Math.log(n) / Math.log(2)));
}

var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
ctx.font = `${config.fontweight} ${config.fontsize}px/${config.lineheight} ${config.fontface}`;
const textMetrics = ctx.measureText(text);
var textWidth = textMetrics.width;
var textHeight = config.fontsize * config.lineheight;
canvas.width = nextPowerOf2(textWidth);
canvas.height = nextPowerOf2(textHeight);
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, textWidth, textHeight);
ctx.fillStyle = 'black';
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.font = `${config.fontweight} ${config.fontsize}px/${config.lineheight} ${config.fontface}`;
ctx.fillText(text, 0, 0);

console.log('canvas', canvas.width, canvas.height);
console.log('text', textWidth, textHeight);

var texture = new THREE.Texture(canvas);
texture.needsUpdate = true;
texture.repeat.set(textWidth / canvas.width, textHeight / canvas.height);
var spriteMaterial = new THREE.SpriteMaterial({
  map: texture
});
var sprite = new THREE.Sprite(spriteMaterial);
sprite.scale.set(textWidth, textHeight, 1);
scene.add(sprite);


var animate = function() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
};

animate();
body { margin: 0; }
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r113/build/three.min.js"></script>