Three.js 画布纹理幂为 2 但被裁剪

Posted

技术标签:

【中文标题】Three.js 画布纹理幂为 2 但被裁剪【英文标题】:Three.js canvas texture power of 2 but cropped 【发布时间】:2020-05-30 19:23:56 【问题描述】:

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

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

考虑这个例子:

黑色 bg 是 Three.js 渲染器 红色 bg 是画布所需的大小(2 次方) 白色 bg 是文字的大小

我想将纹理保持为 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.fontsizepx/$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.fontsizepx/$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?

【问题讨论】:

仅供参考:在旧浏览器上没有也从来没有任何不同。另外,请使用snippet 并为three.js 使用版本化CDN,否则您的示例将在几周内中断并且无用。 已更新为使用 cdn 和 sn-p。有不同/旧版本的 Chrome 不支持它 '251[.WebGL-0x86d0ad00]RENDER WARNING:绑定到纹理单元 0 的纹理不可渲染。它可能不是 2 的幂并且具有不兼容的纹理过滤。请参阅以下主题:github.com/mrdoob/three.js/issues/17879 我在该线程中没有看到任何内容表明我写的内容不正确。 WebGL 一直对过滤有 2 的幂的要求,今天仍然有。自 9 年前发货以来,这一点一直没有改变。 抱歉...我的意思是 android 7.1 Chrome 会引发 javascript 错误,阻止画布渲染,而在更现代的浏览器上只显示纹理调整大小警告。了解这一直是最佳做法! 【参考方案1】:

您可以设置纹理的重复,以便只有使用的画布部分覆盖精灵

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.fontsizepx/$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.fontsizepx/$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>

【讨论】:

也可能需要设置纹理偏移后更改repeat texture.offset = new THREE.Vector2(0, 1 - (textHeight / canvas.height)); 你可以使用texture.offset.set(x,y)

以上是关于Three.js 画布纹理幂为 2 但被裁剪的主要内容,如果未能解决你的问题,请参考以下文章

使用vue学习three.js之加载和使用纹理-在canvas画布上生成噪音图,然后创建凹凸贴图

如何从 Three.js 画布中保存图像?

如何禁用three.js以以2的幂调整图像大小?

Three.js 不渲染纹理

在three.js中动态创建二维文本

Three.js 立方体,每个面都有不同的纹理