我正在尝试使用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 );
}
答案 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 );
}