如何仅在Three.js中渲染透明对象的最接近深度级别?

时间:2019-05-14 10:39:56

标签: javascript three.js glsl rendering shader

我正在尝试使用three.js库在WebGL中以全息图或X射线的方式呈现3D对象。它需要在中心透明(以查看背景,也许以后一些对象会在此体积内),并且边缘具有明亮的不透明颜色。隐藏的背面不应该渲染。我是基于Web的图形的新手,所以我不知道我应该使用GLSL着色器还是应该使用混合选项。对不起,愚蠢的问题。

根据此tutorial,使用自定义发光着色器可以达到相似的结果。但这不能解决背面问题。 我在Blender中获得了足够的外观,创建了一个着色器,该着色器通过将透明深度大于0.5来限制光路,从而消除了此类面孔。 我的Blender材质的There is个节点。有没有办法在webGL中做类似的事情? 当前情况和预期的屏幕截图(第二行)为here

当前,我使用three.js库中的OBJLoader,WebGLRenderer和ShaderMaterial。材料定义如下。 CustomShader.js:

const customBrainShader = () => { return {

uniforms: 
{ 
    "c":   { type: "f", value: 1.0 },
    "p":   { type: "f", value: 1.9 },
    glowColor: { type: "c", value: new THREE.Color(0xcfdfff) },
    viewVector: { type: "v3", value: new Vector3(0, 100, 400) }
},

    vertexShader: vertexShaderSource,
    fragmentShader: fragmentShaderSource,

    side: THREE.FrontSide,
    blending: THREE.AdditiveBlending,
    depthTest: true,
    depthWrite: true,
    opacity: 0.5
}};
export { customBrainShader };

片段着色器:

 uniform vec3 glowColor;
 varying float intensity;

 void main() 
 {
  vec3 glow = glowColor * intensity;
  gl_FragColor = vec4( glow, 1.0 );
 }

顶点着色器:

uniform vec3 viewVector;
uniform float c;
uniform float p;
varying float intensity;

void main() 
{
    vec3 vNormal = normalize( normalMatrix * normal );
vec3 vCamera = vec3(0.0,0.0,1.0);
intensity = pow( c - dot(vNormal, vCamera), p );
    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}

2 个答案:

答案 0 :(得分:1)

如果我没记错的话,下面的效果就是您要达到的目的。

这里发生了一些有趣的事情。

首先,我要设置renderer.autoClear = false,以防止渲染器在调用renderer.render之间清除其缓冲区。这样可以多次调用该函数以多次写入缓冲区。

接下来,我正在这样做。我要渲染两次相同的场景。但是您会注意到,第一次渲染时,我设置的是scene.overrideMaterial,它将替换掉场景中的所有材质。出于替代材料中的原因,我需要这样做。

在替代材料中,我设置为colorWrite: false。这意味着尽管将“渲染”对象,但它不会绘制任何颜色,因此没有可见效果(尚未)。它确实写入深度缓冲区,这正是我们想要的,因为对象将隐藏其背后的东西。这就像在一块魔法玻璃后面隐藏了一些东西。 (我在这里还设置了多边形偏移量以避免z冲突,这完全是另一个主题,因此在此答案中我将不做任何详细介绍。)

最后,我再次使用您定义的着色器材质渲染场景。 noColor渲染遮挡了应遮挡的形状,因此当正面在网格的另一部分后面时,不会出现不必要的渗色。着色器将处理其余部分,从而创建发光效果。

// Your shader code
const fragmentShaderSource = `
 uniform vec3 glowColor;
 varying float intensity;

 void main() 
 {
  vec3 glow = glowColor * intensity;
  gl_FragColor = vec4( glow, 1.0 );
 }
`
const vertexShaderSource = `
uniform vec3 viewVector;
uniform float c;
uniform float p;
varying float intensity;

void main() 
{
  vec3 vNormal = normalize( normalMatrix * normal );
  vec3 vCamera = vec3(0.0,0.0,1.0);
  intensity = pow( c - dot(vNormal, vCamera), p );
  gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`

const customBrainShader = new THREE.ShaderMaterial({
  uniforms: {
    c: {
      value: 1.0
    },
    p: {
      value: 1.9
    },
    glowColor: {
      value: new THREE.Color(0xcfdfff)
    },
    viewVector: {
      value: new THREE.Vector3(0, 100, 400)
    }
  },
  vertexShader: vertexShaderSource,
  fragmentShader: fragmentShaderSource,
  side: THREE.FrontSide,
  opacity: 0.5
})

// male02 model from the three.js examples
const modelPath = "https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/models/obj/male02/male02.obj"

const renderer = new THREE.WebGLRenderer({
  antialias: true
})
renderer.autoClear = false
renderer.setSize(200, 200)
document.body.appendChild(renderer.domElement)

const scene = new THREE.Scene()

const camera = new THREE.PerspectiveCamera(28, 1, 1, 1000)
camera.position.set(0, 90, 500)
const cameraTarget = new THREE.Vector3(0, 90, 0)
camera.lookAt(cameraTarget)

const light = new THREE.PointLight(0xffffff, 1)
camera.add(light)

scene.add(camera)

function render() {
  renderer.clear()
  scene.overrideMaterial = noColor
  renderer.render(scene, camera)
  scene.overrideMaterial = null
  renderer.render(scene, camera)
}

const axis = new THREE.Vector3(0, 1, 0)
const noColor = new THREE.MeshBasicMaterial({
  colorWrite: false,
  polygonOffset: true,
  polygonOffsetUnits: 1,
  polygonOffsetFactor: 1
})

function animate() {
  requestAnimationFrame(animate)
  camera.position.applyAxisAngle(axis, 0.0025)
  camera.lookAt(cameraTarget)
  render()
}
animate()

const loader = new THREE.OBJLoader()
loader.load(modelPath, (results) => {
  results.traverse(node => {
    if (node instanceof THREE.Mesh) {
      node.material = customBrainShader
    }
  })
  scene.add(results)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/104/three.js"></script>
<script src="https://threejs.org/examples/js/loaders/OBJLoader.js"></script>

答案 1 :(得分:0)

事实证明,我的评论想法没有我想的那么糟。 JSFiddle example

为了在片段着色器内部创建深度遮挡剔除,您需要创建一个启用了depthBuffer和depthTexture的唯一WebGLRenderTarget

target = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight );
target.texture.format = THREE.RGBFormat;;
target.stencilBuffer = false;
target.depthBuffer = true;
target.depthTexture = new THREE.DepthTexture();
target.depthTexture.type = THREE.UnsignedShortType;

然后,在动画循环内,需要使用占位符MeshBasicMaterial将网格渲染为常规场景,并将其渲染为WebGLRenderTarget,以更新自定义ShaderMaterial的统一纹理以使用新渲染的depthTexture。

最后,您可以将ShaderMaterial分配给网格并正常渲染场景。

function animate() {

    requestAnimationFrame( animate );

    // render scene into target
    mesh.material = basicMaterial;
    renderer.setRenderTarget( target );
    renderer.render( scene, camera );

    // update custom shader uniform
    shaderMaterial.uniforms.tDepth.value = target.depthTexture;

    // render scene into scene
    mesh.material = shaderMaterial;
    renderer.setRenderTarget( null );
    renderer.render( scene, camera );

}

在片段着色器内部,您需要将当前片段的z位置与depthTexture上的相应像素进行比较。这需要很少的操作就可以在相同的坐标空间中获得两个z值。

#include <packing>

uniform sampler2D tDepth;
uniform vec3 glowColor;
uniform vec2 viewportSize;
uniform float cameraNear;
uniform float cameraFar;

varying float intensity;

float readDepth( sampler2D depthSampler, vec2 coord ) {
    float fragCoordZ = texture2D( depthSampler, coord ).x;
    float viewZ = perspectiveDepthToViewZ( fragCoordZ, cameraNear, cameraFar );
    return viewZToOrthographicDepth( viewZ, cameraNear, cameraFar );
}

void main() {

    float zDepth = readDepth( tDepth, gl_FragCoord.xy / viewportSize.xy );
    float fragDepth = gl_FragCoord.z/gl_FragCoord.w/cameraFar;

    if ( fragDepth > zDepth + 0.001 ) discard; // 0.001 offset to prevent self-culling.

    vec3 glow = glowColor * intensity;
    gl_FragColor = vec4( glow, 1.0 );

}