Three.js - THREE.Points上透明画布纹理映射的depthWrite和depthTest区别

15

问题

depthWrite: falsedepthTest: false之间是否存在显著差异?使用depthTest是否提供性能优势?在选择其中一种时,是否会有任何功能上的牺牲?

原始问题

我想渲染一个THREE.Points对象,每个点都是具有半透明圆形的粒子。我使用从canvas元素加载的 THREE.Texture,并将其传递给THREE.PointsMaterial上的map属性。

透明度并没有完全生效,有些圆圈重叠得很好,但其他圆圈的行为就像它们是实心的。

后来我了解到在THREE.PointsMaterial上使用depthWrite: falsedepthTest: false可以解决这个问题。

目前情况

我有一个代码示例(嵌入在下面),展示了重叠点错误的情况,并可以使用depthTestdepthWrite进行修复:

var points = new THREE.Points(
    new THREE.Geometry(),
    new THREE.PointsMaterial({
        //depthTest: false,
        //depthWrite: false,
        map: circleTexture,
        size: circleDiameter,
        transparent: true
    })
);

我对这一切都很新,但我试着阅读了相关主题,从我所了解的来看(如果我错了,请纠正我),深度缓冲区用于确定哪些片段被遮挡并且不需要渲染。关闭depthWritedepthTest中的任何一个将使对象免于此过程。它们的区别在于:

  • depthWrite:false仍然计算深度,但无论如何都渲染整个对象

  • depthTest:false甚至不会计算深度

这听起来像是关闭depthTest而不是depthWrite将导致我失去一些对象特性,但通过完全跳过计算可能会获得性能提升?但是,我会失去哪些质量呢?实际上是否存在性能差异?这里显露出我的无知。

// Sizes
var sceneWidth = 200;
var sceneHeight = 200;
var lineLength = 50;
var circleRadius = 32;
var circleDiameter = circleRadius * 2;

// Renderer
var renderer = new THREE.WebGLRenderer({
    antialias: true,
    alpha: true
});
renderer.setSize(sceneWidth, sceneHeight);
document.body.appendChild(renderer.domElement);

// Scene
var scene = new THREE.Scene();

// Camera
var d = 100;
var aspect = sceneWidth / sceneHeight;
var camera = new THREE.OrthographicCamera(
    -d * aspect,
    d * aspect,
    d,
    -d,
    1,
    12000
);
camera.position.set(140, 140, 140);
scene.add(camera);

// Controls
var controls = new THREE.OrthographicTrackballControls(
    camera,
    renderer.domElement
);
controls.rotateSpeed = 0.2;
controls.addEventListener('change', function () {
    renderer.render(scene, camera);
});
window.addEventListener('resize', function() {
    controls.handleResize();
});

// Circle texture
var canvasEl = document.createElement('canvas');
var context = canvasEl.getContext('2d');
canvasEl.width = circleDiameter;
canvasEl.height = circleDiameter;
context.fillStyle = 'rgba(255, 255, 255, 0.5)';
context.beginPath();
context.arc(circleRadius, circleRadius, circleRadius, 0, Math.PI * 2);
context.fill();
var circleTexture = new THREE.Texture(canvasEl);
circleTexture.needsUpdate = true;

// Points
var points = new THREE.Points(
    new THREE.Geometry(),
    new THREE.PointsMaterial({
        //depthTest: false,
        //depthWrite: false,
        map: circleTexture,
        size: circleDiameter,
        transparent: true
    })
);
points.geometry.vertices.push(new THREE.Vector3(0, 0, 0));
points.geometry.vertices.push(new THREE.Vector3(0, lineLength, 0));
points.geometry.vertices.push(new THREE.Vector3(0, lineLength, lineLength));
points.geometry.vertices.push(new THREE.Vector3(0, 0, lineLength));
scene.add(points);

// Lines
var lines = new THREE.Line(
    new THREE.Geometry(),
    new THREE.LineBasicMaterial({
        linewidth: 1.2,
        color: 0xffffff,
        transparent: true,
        opacity: 0.25
    })
);
lines.geometry.vertices.push(new THREE.Vector3(0, 0, 0));
lines.geometry.vertices.push(new THREE.Vector3(0, lineLength, 0));
lines.geometry.vertices.push(new THREE.Vector3(0, lineLength, 0));
lines.geometry.vertices.push(new THREE.Vector3(0, lineLength, lineLength));
lines.geometry.vertices.push(new THREE.Vector3(0, lineLength, lineLength));
lines.geometry.vertices.push(new THREE.Vector3(0, 0, lineLength));
lines.geometry.vertices.push(new THREE.Vector3(0, 0, lineLength));
lines.geometry.vertices.push(new THREE.Vector3(0, 0, 0));
scene.add(lines);

// Render
function render() {
    window.requestAnimationFrame(render);
    renderer.render(scene, camera);
    controls.update();
}

render();
* { margin: 0; padding: 0; }
body { background-color: #333; }
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>Document</title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r76/three.min.js"></script>
<script src="http://threejs.org/examples/js/controls/OrthographicTrackballControls.js"></script>
</body>
</html>

1个回答

57

关闭深度测试(depth test off)表示完全关闭深度测试(读取/测试和写入)。

关闭深度写入(depth write off)表示防止深度缓冲区被写入。

那么首先,什么是深度测试?假设你在面前直接绘制两个相同的形状,但距离你不同。在现实生活中,你期望只看到更靠近你的形状,对吧?

如果你没有使用深度测试,你只有一半的机会得到所需的效果:如果远处的对象在更靠近的对象之前绘制,那么没问题,和现实生活一样;但是如果更靠近的对象在距离较远的对象之前绘制,那么问题就来了,距离较远的对象在不应该出现时可见。这很麻烦。

深度测试是今天GPU内置的工具,允许以所绘制对象的顺序无关的方式获得所需的绘制输出。这通常非常有用,但它也有一个关键性的弱点:深度和混合(透明度)不能同时使用。为什么呢?因为深度测试比较每个像素的距离(深度)与该像素中存储的深度值相比较。如果距离小于存储的深度值,就绘制该像素,否则该像素被丢弃。

这解释了为什么有时会在演示中看到黑色的矩形。当这些矩形首先绘制时,它们的深度值被写入深度缓冲区。然后,当更远处的矩形被绘制时,它们的深度值大于缓冲区中的深度,因此这些像素被丢弃。在其他视角中,碰巧是先绘制远处的矩形,然后再绘制更靠近的矩形,因此不会因为深度测试而丢弃像素。

希望现在清楚了,深度测试有两个方面:深度值比较和深度值写入深度缓冲区。深度测试(DepthTest)和深度写入(DepthWrite)可以精细控制如何实现所需的效果。

关闭深度测试比仅关闭深度写入更快。然而,有时您只想防止新像素写入深度缓冲区,但仍保持深度测试启用。例如,在您的演示中,如果您要在中心绘制一个完全不透明的立方体,您仍希望深度大于代表不透明立方体的像素的像素被隐藏(深度测试方面),但也希望防止透明圆形的像素相互阻挡(写入方面)。您经常看到的常见绘制配置是打开深度测试以绘制所有不透明对象,关闭深度写入,然后按从后到前的顺序绘制透明对象。


1
啊,好的,谢谢你的帮助!如果我在我的示例代码中(例如在这里http://jsbin.com/bigikijogi/edit?js,output)投掷一个实心立方体并使用`depthTest: false,当圆形在固体几何体后面时,它们将无法正确遮挡。但是如果我使用depthWrite: false`,它们将被正确地阻挡。 - aburner
1
很棒的答案。它帮助我解决了一个类似的问题,链接在这里:http://stackoverflow.com/questions/42871582/render-100k-sprites-with-0-1-opacity-transparent-maps-and-smooth-antialiasing/42872374#42872374 - Adrian Moisa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接