最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Three.js smooth particles opacity cut on depthTest - Stack Overflow

programmeradmin1浏览0评论

I have a particle system in three.js 0.173.0 with soft particle edges, and when z soring occurs the opaque pixels overwrite the scene rather than additionally drawn on the top, when the particle's order is higher than its original place in the draw order. I've tried a couple of things, double pass, different material settings... but the problem seemed to persist. I've made a codepen demonstrating the issue. Please check it out and leave a comment if you have any ideas how to resolve it.

Codepen example

import * as THREE from ";;

let camera, scene, renderer, material, points;

const vertexShader = `
attribute float u_id;
varying vec2 vUv;
varying vec3 vColor;

void main() {
    vec3 newPosition = position;

    //
    // THE CIRCLES HAVE TO BE POSITIONED IN THE SHADER
    //
    
    if (u_id == 0.0) {
        newPosition = vec3(-0.6, 0.0, 1.2);
        vColor = vec3(1.0, 0.0, 0.0);
    }
    if (u_id == 1.0) {
        newPosition = vec3(0.0, 0.0, 1.0);
        vColor = vec3(0.0, 1.0, 0.0);
    }
    if (u_id == 2.0) {
        newPosition = vec3(0.6, 0.0, 1.1);
        vColor = vec3(0.0, 0.0, 1.0);
    }
    vUv = newPosition.xy;
    gl_PointSize = 200.0; // Control the circle size
    gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
}
`;

const fragmentShader = `
precision mediump float;
varying vec2 vUv;
varying vec3 vColor;

void main() {
    vec2 uv = gl_PointCoord - vec2(0.5); // Center the UVs in the circle
    float dist = length(uv);
    float alpha = smoothstep(0.5, 0.1, dist); // Smooth fade at edges

    vec3 color = vColor; //vec3(1.0, 0.0, 0.0); // Red color

    if (dist > 0.5) discard; // Make the circle smooth
    gl_FragColor = vec4(color, alpha);
}
`;

const VERTEX_COUNT = 3;

// Initial vertex positions are [0, 0, 0]
const getInitialVertices = () => {
    const vertices = new Float32Array(VERTEX_COUNT * 3);
    return vertices;
};

// Generate vertex IDs
const getVertexIds = () => {
    const ids = new Float32Array(VERTEX_COUNT);
    for (let i = 0; i < VERTEX_COUNT; i++) {
        ids[i] = i;
    }
    return ids;
};

function init() {
    scene = new THREE.Scene();

    camera = new THREE.PerspectiveCamera(
        50,
        window.innerWidth / window.innerHeight,
        1,
        10000
    );
    camera.position.z = 4;
    scene.add(camera);

    const uniforms = {};

    const geometry = new THREE.BufferGeometry();
    geometry.setAttribute("position", new THREE.BufferAttribute(getInitialVertices(), 3));
    geometry.setAttribute("u_id", new THREE.BufferAttribute(getVertexIds(), 1));

    material = new THREE.ShaderMaterial({
        uniforms: uniforms,
        vertexShader: vertexShader,
        fragmentShader: fragmentShader,
        transparent: true,
        //
        // THE OPACITY CUT OCCURES WHEN DEPTH TEST IS ON
        //
        depthTest: true,
    });

    points = new THREE.Points(geometry, material);
    scene.add(points);

    renderer = new THREE.WebGLRenderer({ alpha: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);
}

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

window.addEventListener("load", () => {
    init();
    animate();
});

I have a particle system in three.js 0.173.0 with soft particle edges, and when z soring occurs the opaque pixels overwrite the scene rather than additionally drawn on the top, when the particle's order is higher than its original place in the draw order. I've tried a couple of things, double pass, different material settings... but the problem seemed to persist. I've made a codepen demonstrating the issue. Please check it out and leave a comment if you have any ideas how to resolve it.

Codepen example

import * as THREE from "https://esm.sh/three";

let camera, scene, renderer, material, points;

const vertexShader = `
attribute float u_id;
varying vec2 vUv;
varying vec3 vColor;

void main() {
    vec3 newPosition = position;

    //
    // THE CIRCLES HAVE TO BE POSITIONED IN THE SHADER
    //
    
    if (u_id == 0.0) {
        newPosition = vec3(-0.6, 0.0, 1.2);
        vColor = vec3(1.0, 0.0, 0.0);
    }
    if (u_id == 1.0) {
        newPosition = vec3(0.0, 0.0, 1.0);
        vColor = vec3(0.0, 1.0, 0.0);
    }
    if (u_id == 2.0) {
        newPosition = vec3(0.6, 0.0, 1.1);
        vColor = vec3(0.0, 0.0, 1.0);
    }
    vUv = newPosition.xy;
    gl_PointSize = 200.0; // Control the circle size
    gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
}
`;

const fragmentShader = `
precision mediump float;
varying vec2 vUv;
varying vec3 vColor;

void main() {
    vec2 uv = gl_PointCoord - vec2(0.5); // Center the UVs in the circle
    float dist = length(uv);
    float alpha = smoothstep(0.5, 0.1, dist); // Smooth fade at edges

    vec3 color = vColor; //vec3(1.0, 0.0, 0.0); // Red color

    if (dist > 0.5) discard; // Make the circle smooth
    gl_FragColor = vec4(color, alpha);
}
`;

const VERTEX_COUNT = 3;

// Initial vertex positions are [0, 0, 0]
const getInitialVertices = () => {
    const vertices = new Float32Array(VERTEX_COUNT * 3);
    return vertices;
};

// Generate vertex IDs
const getVertexIds = () => {
    const ids = new Float32Array(VERTEX_COUNT);
    for (let i = 0; i < VERTEX_COUNT; i++) {
        ids[i] = i;
    }
    return ids;
};

function init() {
    scene = new THREE.Scene();

    camera = new THREE.PerspectiveCamera(
        50,
        window.innerWidth / window.innerHeight,
        1,
        10000
    );
    camera.position.z = 4;
    scene.add(camera);

    const uniforms = {};

    const geometry = new THREE.BufferGeometry();
    geometry.setAttribute("position", new THREE.BufferAttribute(getInitialVertices(), 3));
    geometry.setAttribute("u_id", new THREE.BufferAttribute(getVertexIds(), 1));

    material = new THREE.ShaderMaterial({
        uniforms: uniforms,
        vertexShader: vertexShader,
        fragmentShader: fragmentShader,
        transparent: true,
        //
        // THE OPACITY CUT OCCURES WHEN DEPTH TEST IS ON
        //
        depthTest: true,
    });

    points = new THREE.Points(geometry, material);
    scene.add(points);

    renderer = new THREE.WebGLRenderer({ alpha: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);
}

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

window.addEventListener("load", () => {
    init();
    animate();
});
Share Improve this question edited Mar 7 at 14:27 AmirHossein_Khakshouri 4461 gold badge4 silver badges13 bronze badges asked Mar 7 at 9:36 David SzucsDavid Szucs 656 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 2

Try set

material.depthWrite = false;

Thanks to this, the particles do not save their position in the depth buffer and the transparent particles are correctly put on each other "...without creating z-index artifacts."

https://threejs./docs/#api/en/materials/Material.depthWrite

<script type="importmap">
  {
"imports": {
  "three": "https://unpkg/[email protected]/build/three.module.js",
  "three/addons/": "https://unpkg/[email protected]/examples/jsm/"
}
  }
</script>

<script type="module">
import * as THREE from 'three';

let camera, scene, renderer, material, points;

const vertexShader = `
attribute float u_id;
varying vec2 vUv;
varying vec3 vColor;

void main() {
vec3 newPosition = position;

//
// THE CIRCLES HAVE TO BE POSITIONED IN THE SHADER
//

if (u_id == 0.0) {
    newPosition = vec3(-0.6, 0.0, 1.2);
    vColor = vec3(1.0, 0.0, 0.0);
}
if (u_id == 1.0) {
    newPosition = vec3(0.0, 0.0, 1.0);
    vColor = vec3(0.0, 1.0, 0.0);
}
if (u_id == 2.0) {
    newPosition = vec3(0.6, 0.0, 1.1);
    vColor = vec3(0.0, 0.0, 1.0);
}
vUv = newPosition.xy;
gl_PointSize = 200.0; // Control the circle size
gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
}
`;
const fragmentShader = `
precision mediump float;
varying vec2 vUv;
varying vec3 vColor;

void main() {
vec2 uv = gl_PointCoord - vec2(0.5); // Center the UVs in the circle
float dist = length(uv);
float alpha = smoothstep(0.5, 0.1, dist); // Smooth fade at edges

vec3 color = vColor; //vec3(1.0, 0.0, 0.0); // Red color

if (dist > 0.5) discard; // Make the circle smooth
gl_FragColor = vec4(color, alpha);
}
`;

const VERTEX_COUNT = 3;

// Initial vertex positions are [0, 0, 0]
const getInitialVertices = () => {
const vertices = new Float32Array(VERTEX_COUNT * 3);
return vertices;
};

// Generate vertex IDs
const getVertexIds = () => {
const ids = new Float32Array(VERTEX_COUNT);
for (let i = 0; i < VERTEX_COUNT; i++) {
    ids[i] = i;
}
return ids;
};
function init() {
scene = new THREE.Scene();

camera = new THREE.PerspectiveCamera(
    50,
    window.innerWidth / window.innerHeight,
    1,
    10000
);
camera.position.z = 4;
scene.add(camera);

const uniforms = {};

const geometry = new THREE.BufferGeometry();
geometry.setAttribute("position", new THREE.BufferAttribute(getInitialVertices(), 3));
geometry.setAttribute("u_id", new THREE.BufferAttribute(getVertexIds(), 1));

material = new THREE.ShaderMaterial({
    uniforms: uniforms,
    vertexShader: vertexShader,
    fragmentShader: fragmentShader,
    transparent: true,
    //
    // THE OPACITY CUT OCCURES WHEN DEPTH TEST IS ON
    //
    depthTest: true,
});
  
material.depthWrite = false;

points = new THREE.Points(geometry, material);
scene.add(points);

renderer = new THREE.WebGLRenderer({ alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}

window.addEventListener("load", () => {
init();
animate();
});

</script>

Edit

I messed around and search a little bit... because with these output parameters, using transparency can be tricky. It seems that the approach where z-sorting depends on the camera position and later subsequent adjustment of the fragment is the most sensible (because you wrote that you will not manipulate the camera). The solution is not perfect (because there is no such solution at the moment, as far as I know), but it is probably the most sensible, apart from some sophisticated ones.

Also take a look at this thread, because that's where I got the code.

https://discourse.threejs./t/need-help-making-transparency-show-correctly-in-point-cloud/51971

Also maybe this will be interested for you

https://developer.nvidia/gpugems/gpugems2/part-vi-simulation-and-numerical-algorithms/chapter-46-improved-gpu-sorting

*{margin:0}
<script type="importmap">
  {
"imports": {
  "three": "https://unpkg/[email protected]/build/three.module.js",
  "three/addons/": "https://unpkg/[email protected]/examples/jsm/"
}
  }
</script>

<script type="module">
import * as THREE from 'three';

let camera, scene, renderer, points;

function init() {
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0xffffff);
  camera = new THREE.PerspectiveCamera(
    50,
    window.innerWidth / window.innerHeight,
    1,
    10000
  );

  camera.position.z = 10;

  const geometry = new THREE.BufferGeometry();
  const numPoints = 3;
  // ppositions for three points _ particles:
  // Red: (-0.5, 0, 1.2), Blue: (0.5, 0, 1.1), Green: (0, 0, 1.0)
  const positions = new Float32Array([-0.5, 0, 1.2, 0.5, 0, 1.1, 0.0, 0, 1.0]);
  const colors = new Float32Array([1, 0, 0, 0, 0, 1, 0, 1, 0]);
  geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
  geometry.setAttribute("customColor", new THREE.BufferAttribute(colors, 3));

  const vertexShader = `
    attribute vec3 customColor;
    varying vec3 vColor;
    void main(){
      vColor = customColor;
      gl_PointSize = 80.0;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `;
  const fragmentShader = `
    precision mediump float;
    varying vec2 vUv;
    varying vec3 vColor;

    void main() {
    vec2 uv = gl_PointCoord - vec2(0.5); // Center the UVs in the circle
    float dist = length(uv);
    float alpha = smoothstep(0.5, 0.1, dist); // Smooth fade at edges

    vec3 color = vColor; //vec3(1.0, 0.0, 0.0); // Red color

    if (dist > 0.5) discard; // Make the circle smooth
    gl_FragColor = vec4(color, alpha);
    }
  `;
  const material = new THREE.ShaderMaterial({
    vertexShader: vertexShader,
    fragmentShader: fragmentShader,
    transparent: true,
    depthTest: true,
  });

  points = new THREE.Points(geometry, material);
  scene.add(points);

  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);
}
function depthSortGeometry(geometry, camera) {
  const positionAttribute = geometry.attributes.position;
  const colorAttribute = geometry.attributes.customColor;

  const depthArray = Array.from({ length: positionAttribute.count }, (_, i) => {
    const pos = new THREE.Vector3().fromBufferAttribute(positionAttribute, i);
    return camera.position.distanceTo(pos);
  });

  const indices = depthArray
    .map((depth, i) => i)
    .sort((a, b) => depthArray[b] - depthArray[a]);

  const newPositionAttribute = new THREE.BufferAttribute(
    new Float32Array(positionAttribute.count * 3),
    3
  );
  const newColorAttribute = new THREE.BufferAttribute(
    new Float32Array(colorAttribute.count * 3),
    3
  );
  for (let i = 0; i < indices.length; i++) {
    newPositionAttribute.setXYZ(
      i,
      positionAttribute.getX(indices[i]),
      positionAttribute.getY(indices[i]),
      positionAttribute.getZ(indices[i])
    );
    newColorAttribute.setXYZ(
      i,
      colorAttribute.getX(indices[i]),
      colorAttribute.getY(indices[i]),
      colorAttribute.getZ(indices[i])
    );
  }

  const sortedGeometry = new THREE.BufferGeometry();
  sortedGeometry.setAttribute("position", newPositionAttribute);
  sortedGeometry.setAttribute("customColor", newColorAttribute);

  return sortedGeometry;
}

function animate() {
  requestAnimationFrame(animate);

  const sortedGeometry = depthSortGeometry(points.geometry, camera);
  points.geometry.dispose();
  points.geometry = sortedGeometry;

  renderer.render(scene, camera);
}

window.addEventListener("load", () => {
init();
animate();
});
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});

</script>

发布评论

评论列表(0)

  1. 暂无评论