我想写一个小程序,例如计算素数。我的想法是使用webgl/threejs将所有表示素数(基于坐标)的像素染成白色,其余的则表示黑色。我的问题是,webgl以某种方式混合了像素级的颜色(如果在黑色画布的中间有一个白色像素,则它与环境混合,而该白色像素周围的像素变成灰色)。有了这个问题,我无法正确地识别白色和黑色像素。
我的问题是:如何让webgl或画布在不混合的情况下为每个像素使用正确的颜色?
下面是一个演示,我将第二个像素涂成白色:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ProjectLaGPU</title>
</head>
<body>
<script id="vertex-shader" type="x-shader/x-vertex">
uniform float u_start;
varying float v_start;
void main() {
v_start = u_start;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
varying float v_start;
void main () {
if (int(gl_FragCoord.x)%2==0 && int(gl_FragCoord.y)%2==0) {
gl_FragColor = vec4(1, 1, 1, 1);
} else {
gl_FragColor = vec4(0, 0, 0, 1);
}
}
</script>
<script src="https://threejs.org/build/three.js"></script>
<script src="main.js"></script>
</body>
</html>/* main.js */
async function init() {
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-.5, .5, .5, -.5, -1, 1);
const renderer = new THREE.WebGLRenderer({
preserveDrawingBuffer: true,
powerPreference: "high-performance",
depth: false,
stencil: false
});
const geometry = new THREE.PlaneGeometry();
const material = new THREE.ShaderMaterial({
uniforms: {
u_start: { value: 0.5 },
},
vertexShader: document.getElementById("vertex-shader").innerText,
fragmentShader: document.getElementById("fragment-shader").innerText,
});
const mesh = new THREE.Mesh(geometry, material);
renderer.setSize(1000, 1000);
document.body.appendChild(renderer.domElement);
scene.add(mesh)
renderer.render(scene, camera);
}
init();发布于 2020-12-24 08:32:52
以前接受的答案是不正确的,只能靠运气.换句话说,如果它对您有效,那么幸运的是,您的计算机/浏览器/OS/current-设置似乎工作正常。更改一个设置,切换到另一个浏览器/os/机器,它就不能工作了。
这里有许多问题被混为一谈。
1.无模糊画布
有两件事你必须做才能得到一个非模糊的画布。
关闭浏览器的过滤
在Chrome/Edge/Safari中,您可以在火狐canvas { image-rendering: crisp-edges; }上使用canvas { image-rendering: crisp-edges; }进行此操作
请注意,对于这种情况,pixelated比crisp-edges更正确。根据规范,给出了这个原始图像

并将其显示为默认算法大小的3倍,取决于浏览器,但一个典型的示例可能如下所示

pixelated看起来会像这样

的重要性:参见 crisp-edges底部的更新如下所示

但是请注意,用于绘制图像的实际算法不是由规范定义的,因此目前火狐碰巧对crisp-edges使用了最近邻过滤,并且不支持pixelated。因此,您应该最后指定pixelated,这样如果存在crisp-edges,它就会重写它。
帆布有两种尺寸。它的绘图缓冲区大小(画布中的像素数)和显示大小(由CSS定义)。但是CSS大小以CSS像素计算,然后由当前devicePixelRatio缩放。这意味着,如果您没有放大或缩小,并且您的设备上的devicePixelRatio不是1.0,并且绘图缓冲区大小与您的显示大小相匹配,那么画布将被缩放到匹配devicePixelRatio所需的范围。因此,将使用image-rendering CSS样式来决定如何进行缩放。
示例
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-.5, .5, .5, -.5, -1, 1);
const renderer = new THREE.WebGLRenderer();
const geometry = new THREE.PlaneBufferGeometry();
const material = new THREE.ShaderMaterial({
vertexShader: document.getElementById("vertex-shader").innerText,
fragmentShader: document.getElementById("fragment-shader").innerText,
});
const mesh = new THREE.Mesh(geometry, material);
document.body.appendChild(renderer.domElement);
scene.add(mesh)
renderer.render(scene, camera);canvas {
image-rendering: crisp-edges;
image-rendering: pixelated; /* pixelated is more correct so put that last */
}<script src="https://cdn.jsdelivr.net/npm/three@0.123/build/three.js"></script>
<script id="vertex-shader" type="x-shader/x-vertex">
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
void main () {
if (int(gl_FragCoord.x)%2==0 && int(gl_FragCoord.y)%2==0) {
gl_FragColor = vec4(1, 1, 1, 1);
} else {
gl_FragColor = vec4(0, 0, 0, 1);
}
}
</script>
关闭反走样。
Three.js为你做这件事
2.获取画布中的1像素=到屏幕上的1像素
这就是给setPixelRatio打电话的目的。它似乎对你有效只是运气。换句话说,当您尝试在不同的机器或不同的浏览器上运行您的代码时,它可能无法工作。
问题是为了设置画布的绘图缓冲区的大小,使其匹配使用多少像素来显示画布,您必须问浏览器“您使用了多少像素”。
例如,假设有一个设备像素比(dpr)为3,假设窗口宽为100个设备像素。这是33.3333 CSS像素宽。如果您查看window.innerWidth (反正你也不应该这么做),它将报告33个CSS像素。如果您然后乘以您的dpr 3,您得到99,但在现实中,浏览器可能/将画100。
要问实际使用的大小,您必须使用ResizeObserver,并让它通过devicePixelContentBoxSize报告确切的大小
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-.5, .5, .5, -.5, -1, 1);
const renderer = new THREE.WebGLRenderer();
const geometry = new THREE.PlaneBufferGeometry();
const material = new THREE.ShaderMaterial({
vertexShader: document.getElementById("vertex-shader").innerText,
fragmentShader: document.getElementById("fragment-shader").innerText,
});
const mesh = new THREE.Mesh(geometry, material);
function onResize(entries) {
for (const entry of entries) {
let width;
let height;
let dpr = window.devicePixelRatio;
if (entry.devicePixelContentBoxSize) {
// NOTE: Only this path gives the correct answer
// The other 2 paths are an imperfect fallback
// for browsers that don't provide anyway to do this
width = entry.devicePixelContentBoxSize[0].inlineSize;
height = entry.devicePixelContentBoxSize[0].blockSize;
dpr = 1; // it's already in width and height
} else if (entry.contentBoxSize) {
if (entry.contentBoxSize[0]) {
width = entry.contentBoxSize[0].inlineSize;
height = entry.contentBoxSize[0].blockSize;
} else {
width = entry.contentBoxSize.inlineSize;
height = entry.contentBoxSize.blockSize;
}
} else {
width = entry.contentRect.width;
height = entry.contentRect.height;
}
// IMPORTANT! You must pass false here otherwise three.js
// will mess up the CSS!
renderer.setSize(Math.round(width * dpr), Math.round(height * dpr), false);
renderer.render(scene, camera);
}
}
const canvas = renderer.domElement;
const resizeObserver = new ResizeObserver(onResize);
try {
// because some browers don't support this yet
resizeObserver.observe(canvas, {box: 'device-pixel-content-box'});
} catch (e) {
resizeObserver.observe(canvas, {box: 'content-box'});
}
document.body.appendChild(renderer.domElement);
scene.add(mesh)
renderer.render(scene, camera);html, body, canvas {
margin: 0;
width: 100%;
height: 100%;
display: block;
}<script src="https://cdn.jsdelivr.net/npm/three@0.123/build/three.js"></script>
<script id="vertex-shader" type="x-shader/x-vertex">
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
void main () {
if (int(gl_FragCoord.x)%2==0 && int(gl_FragCoord.y)%2==0) {
gl_FragColor = vec4(1, 1, 1, 1);
} else {
gl_FragColor = vec4(0, 0, 0, 1);
}
}
</script>
任何其他方法只会在某些时候起作用。
为了证明这一点,下面是使用setPixelRatio和window.innerWidth与使用ResizeObserver的方法
function test(parent, fn) {
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-.5, .5, .5, -.5, -1, 1);
const renderer = new THREE.WebGLRenderer();
const geometry = new THREE.PlaneBufferGeometry();
const material = new THREE.ShaderMaterial({
vertexShader: document.getElementById("vertex-shader").innerText,
fragmentShader: document.getElementById("fragment-shader").innerText,
});
const mesh = new THREE.Mesh(geometry, material);
fn(renderer, scene, camera);
parent.appendChild(renderer.domElement);
scene.add(mesh)
renderer.render(scene, camera);
}
function resizeOberserverInit(renderer, scene, camera) {
function onResize(entries) {
for (const entry of entries) {
let width;
let height;
let dpr = window.devicePixelRatio;
if (entry.devicePixelContentBoxSize) {
// NOTE: Only this path gives the correct answer
// The other 2 paths are an imperfect fallback
// for browsers that don't provide anyway to do this
width = entry.devicePixelContentBoxSize[0].inlineSize;
height = entry.devicePixelContentBoxSize[0].blockSize;
dpr = 1; // it's already in width and height
} else if (entry.contentBoxSize) {
if (entry.contentBoxSize[0]) {
width = entry.contentBoxSize[0].inlineSize;
height = entry.contentBoxSize[0].blockSize;
} else {
width = entry.contentBoxSize.inlineSize;
height = entry.contentBoxSize.blockSize;
}
} else {
width = entry.contentRect.width;
height = entry.contentRect.height;
}
// IMPORTANT! You must pass false here otherwise three.js
// will mess up the CSS!
renderer.setSize(Math.round(width * dpr), Math.round(height * dpr), false);
renderer.render(scene, camera);
}
}
const canvas = renderer.domElement;
const resizeObserver = new ResizeObserver(onResize);
try {
// because some browers don't support this yet
resizeObserver.observe(canvas, {box: 'device-pixel-content-box'});
} catch (e) {
resizeObserver.observe(canvas, {box: 'content-box'});
}
}
function innerWidthDPRInit(renderer, scene, camera) {
renderer.setPixelRatio(window.deivcePixelRatio);
renderer.setSize(window.innerWidth, 70);
window.addEventListener('resize', () => {
renderer.setPixelRatio(window.deivcePixelRatio);
renderer.setSize(window.innerWidth, 70);
renderer.render(scene, camera);
});
}
test(document.querySelector('#innerWidth-dpr'), innerWidthDPRInit);
test(document.querySelector('#resizeObserver'), resizeOberserverInit);body {
margin: 0;
}
canvas {
height: 70px;
display: block;
}
#resizeObserver,
#resizeObserver>canvas {
width: 100%;
}<script src="https://cdn.jsdelivr.net/npm/three@0.123/build/three.js"></script>
<script id="vertex-shader" type="x-shader/x-vertex">
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
void main () {
if (int(gl_FragCoord.x)%2==0 && int(gl_FragCoord.y)%2==0) {
gl_FragColor = vec4(1, 1, 1, 1);
} else {
gl_FragColor = vec4(0, 0, 0, 1);
}
}
</script>
<div>via window.innerWidth and setPixelRatio</div>
<div id="innerWidth-dpr"></div>
<div>via ResizeObserver</div>
<div id="resizeObserver"></div>
尝试放大浏览器或尝试不同的设备。
更新
规范在2021年2月发生了变化。。crisp-edges现在的意思是“使用最近的邻居”,而pixelated的意思是“保持它看起来像像素化的”,这可以翻译为“如果您想做比最近的邻居更好的事情保持图像像素化”。请参阅这个答案
发布于 2020-12-23 11:23:03
这是不配置设备像素比的典型副作用。这意味着您的应用程序不一定在本地解决方案中运行。下面是预期的结果(我已经做了一个屏幕截图,并检查了Gimp中的颜色值)。
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-.5, .5, .5, -.5, -1, 1);
const renderer = new THREE.WebGLRenderer();
const geometry = new THREE.PlaneBufferGeometry();
const material = new THREE.ShaderMaterial({
vertexShader: document.getElementById("vertex-shader").innerText,
fragmentShader: document.getElementById("fragment-shader").innerText,
});
const mesh = new THREE.Mesh(geometry, material);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
scene.add(mesh)
renderer.render(scene, camera);body {
margin: 0;
}<script src="https://cdn.jsdelivr.net/npm/three@0.123/build/three.js"></script>
<script id="vertex-shader" type="x-shader/x-vertex">
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
void main () {
if (int(gl_FragCoord.x)%2==0 && int(gl_FragCoord.y)%2==0) {
gl_FragColor = vec4(1, 1, 1, 1);
} else {
gl_FragColor = vec4(0, 0, 0, 1);
}
}
</script>
https://stackoverflow.com/questions/65423254
复制相似问题