首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >WebGL在像素级别上混合颜色

WebGL在像素级别上混合颜色
EN

Stack Overflow用户
提问于 2020-12-23 11:05:12
回答 2查看 406关注 0票数 3

我想写一个小程序,例如计算素数。我的想法是使用webgl/threejs将所有表示素数(基于坐标)的像素染成白色,其余的则表示黑色。我的问题是,webgl以某种方式混合了像素级的颜色(如果在黑色画布的中间有一个白色像素,则它与环境混合,而该白色像素周围的像素变成灰色)。有了这个问题,我无法正确地识别白色和黑色像素。

我的问题是:如何让webgl或画布在不混合的情况下为每个像素使用正确的颜色?

下面是一个演示,我将第二个像素涂成白色:

代码语言:javascript
复制
<!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>
代码语言:javascript
复制
/* 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();
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-12-24 08:32:52

以前接受的答案是不正确的,只能靠运气.换句话说,如果它对您有效,那么幸运的是,您的计算机/浏览器/OS/current-设置似乎工作正常。更改一个设置,切换到另一个浏览器/os/机器,它就不能工作了。

这里有许多问题被混为一谈。

1.无模糊画布

有两件事你必须做才能得到一个非模糊的画布。

关闭浏览器的过滤

在Chrome/Edge/Safari中,您可以在火狐canvas { image-rendering: crisp-edges; }上使用canvas { image-rendering: crisp-edges; }进行此操作

请注意,对于这种情况,pixelatedcrisp-edges更正确。根据规范,给出了这个原始图像

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

pixelated看起来会像这样

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

但是请注意,用于绘制图像的实际算法不是由规范定义的,因此目前火狐碰巧对crisp-edges使用了最近邻过滤,并且不支持pixelated。因此,您应该最后指定pixelated,这样如果存在crisp-edges,它就会重写它。

帆布有两种尺寸。它的绘图缓冲区大小(画布中的像素数)和显示大小(由CSS定义)。但是CSS大小以CSS像素计算,然后由当前devicePixelRatio缩放。这意味着,如果您没有放大或缩小,并且您的设备上的devicePixelRatio不是1.0,并且绘图缓冲区大小与您的显示大小相匹配,那么画布将被缩放到匹配devicePixelRatio所需的范围。因此,将使用image-rendering CSS样式来决定如何进行缩放。

示例

代码语言:javascript
复制
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);
代码语言:javascript
复制
canvas { 
  image-rendering: crisp-edges;
  image-rendering: pixelated;  /* pixelated is more correct so put that last */
}
代码语言:javascript
复制
<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报告确切的大小

代码语言:javascript
复制
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);
代码语言:javascript
复制
html, body, canvas {
  margin: 0;
  width: 100%;
  height: 100%;
  display: block;
}
代码语言:javascript
复制
<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的方法

代码语言:javascript
复制
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);
代码语言:javascript
复制
body {
  margin: 0;
}
canvas {
  height: 70px;
  display: block;
}
#resizeObserver,
#resizeObserver>canvas {
  width: 100%;
}
代码语言:javascript
复制
<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的意思是“保持它看起来像像素化的”,这可以翻译为“如果您想做比最近的邻居更好的事情保持图像像素化”。请参阅这个答案

票数 4
EN

Stack Overflow用户

发布于 2020-12-23 11:23:03

这是不配置设备像素比的典型副作用。这意味着您的应用程序不一定在本地解决方案中运行。下面是预期的结果(我已经做了一个屏幕截图,并检查了Gimp中的颜色值)。

代码语言:javascript
复制
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);
代码语言:javascript
复制
body {
      margin: 0;
}
代码语言:javascript
复制
<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>

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65423254

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档