为了正确显示半透明的模型,各路前辈们研究了很多方法。最常用的OIT方法,在Three.js 中没有提供相关的模块,经过几天的研究,结合两位大神 WebGL 九浅一深 和 Night_Aurora 的总结,在Three中做了简单的实现。
Three.js中实现OIT的关键代码
创建RenderTarget,必须是浮点数纹理,需要使用WebGL2
function createRenderTargets() {
//需要使用浮点数纹理
let data_type = THREE.FloatType
if( renderer.extensions.get( 'OES_texture_float_linear' ) === null ) data_type = THREE.HalfFloatType
var dpr = renderer.getPixelRatio();
let w = window.innerWidth * dpr
let h = window.innerHeight * dpr
//颜色累加 render target
colorTarget = new THREE.WebGLRenderTarget(
w, h,
{
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
//wrapS: THREE.ClampToEdgeWrapping,
//wrapT: THREE.ClampToEdgeWrapping
fromat: THREE.RGBAFormat,
type:data_type
})
// alpha累加
alphaTarget = new THREE.WebGLRenderTarget(
w, h,
{
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
//fromat: THREE.RGBAFormat,
fromat: THREE.RedFormat,
type:data_type
})
//渲染不透明物体
opaqueTarget = new THREE.WebGLRenderTarget(
w, h,
{
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
fromat: THREE.RGBAFormat,
type: data_type
})
opaqueTarget.stencilBuffer = false;
opaqueTarget.depthBuffer = true;
opaqueTarget.depthTexture = new THREE.DepthTexture();
opaqueTarget.depthTexture.type = THREE.FloatType;
}
创建OIT 用的材质,主要功能就是设置渲染状态,以及替换shader代码。
//OIT 材质 克隆原透明材质
materialColor = material.clone();
//设置混合参数
materialColor.blending = THREE.CustomBlending
materialColor.blendSrc = THREE.OneFactor
materialColor.blendDst = THREE.OneFactor
materialColor.blendSrcAlpha = THREE.ZeroFactor
materialColor.blendDstAlpha = THREE.OneMinusSrcAlphaFactor
//materialColor.blendEquation = THREE.AddEquation
materialColor.depthWrite = false;
materialColor.depthTest = false
materialColor.depthFunc = THREE.AlwaysDepth
//设置回调函数编译前 替换shader代码
materialColor.onBeforeCompile = colorOnBeforeCompile;
//OIT alpha材质
materialAlpha = materialColor.clone();
materialAlpha.onBeforeCompile = alphaOnBeforeCompile;
..................
//替换shader
function colorOnBeforeCompile(shader) {
shader.uniforms.uOpaqueDepth = globalPeelUniforms.uOpaqueDepth
shader.uniforms.uScreenSize = globalPeelUniforms.uScreenSize
//改颜色
shader.fragmentShader = shader.fragmentShader.replace(
/}$/gm,
`
float w = weight(gl_FragCoord.z, gl_FragColor.a);
gl_FragColor.rgb = gl_FragColor.rgb * gl_FragColor.a;
gl_FragColor = vec4(gl_FragColor.rgb * w, gl_FragColor.a);
vec2 screenPos = gl_FragCoord.xy * uScreenSize;
vec4 dep = texture2D( uOpaqueDepth, screenPos );
//float dddd = unpackRGBAToDepth(dep);
float dddd = dep.r;
if (gl_FragCoord.z > dddd)
discard;
}
`
)
weightShader(shader);
}
function alphaOnBeforeCompile(shader) {
shader.uniforms.uOpaqueDepth = globalPeelUniforms.uOpaqueDepth
shader.uniforms.uScreenSize = globalPeelUniforms.uScreenSize
//改颜色
shader.fragmentShader = shader.fragmentShader.replace(
/}$/gm,
`
float w = weight(gl_FragCoord.z, gl_FragColor.a);
gl_FragColor = vec4(gl_FragColor.a*w, gl_FragColor.a*w, gl_FragColor.a*w, gl_FragColor.a*w);
vec2 screenPos = gl_FragCoord.xy * uScreenSize;
vec4 dep = texture2D( uOpaqueDepth, screenPos );
float dddd = dep.r;
//float dddd = unpackRGBAToDepth(dep);
if (gl_FragCoord.z > dddd)
discard;
//gl_FragColor.rgb = vec3(1,0,0);
}
`
)
weightShader(shader);
}
function weightShader(shader) {
shader.fragmentShader = shader.fragmentShader.replace('#include ' ,'')
shader.fragmentShader = `
#include
uniform sampler2D uOpaqueDepth;
uniform vec2 uScreenSize;
//calc weight
float weight(float z, float a) {
return clamp(pow(min(1.0, a * 10.0) + 0.01, 3.0) * 1e8 * pow(1.0 - z * 0.9, 3.0), 1e-2, 3e3);
}
${
shader.fragmentShader}
`
}
加权求平均,最终与不透明的像素进行混合
<script type="x-shader/x-vertex" id="vertexShader">
varying vec2 vUv;
void main()
{
vUv = uv;
gl_Position = vec4(position.xy, 0.0, 1.0);
}
</script>
<script type="x-shader/x-fragment" id="fragmentShader">
//precision highp float;
varying vec2 vUv;
uniform sampler2D uAccumulate;
uniform sampler2D uAccumulateAlpha;
uniform sampler2D uOpaque;
void main() {
vec4 accum = texture2D( uAccumulate, vUv );
float r = accum.a;
accum.a = texture2D(uAccumulateAlpha, vUv).r;
vec4 color = vec4(accum.rgb / clamp(accum.a, 0.0001, 50000.0), r);
color.rgb = pow(color.rgb, vec3(1.0/2.2));
color = vec4((1.0-r) * accum.rgb / clamp(accum.a, 0.001, 50000.0), r);
vec4 opaqueColor = texture2D(uOpaque, vUv).rgba;
vec3 outputColor = mix(color.rgb, opaqueColor.rgb, color.a);
gl_FragColor = vec4(outputColor.rgb, 1);
}
</script>
//---------------------------------------
//两帧混合
pmaterial = new THREE.ShaderMaterial( {
uniforms: {
"uAccumulate": {
value: null },
"uAccumulateAlpha": {
value: null },
"uOpaque": {
value: null }
},
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent
} );
pmaterial.blending = THREE.CustomBlending
//material.blending = THREE.NoBlending
pmaterial.blendSrc = THREE.OneFactor
pmaterial.blendDst = THREE.OneMinusSrcAlphaFactor
//material.blendSrcAlpha = THREE.ZeroFactor
//material.blendDstAlpha = THREE.OneMinusSrcAlphaFactor
渲染函数
TransparencyObjects.forEach(o=>o.visible = false)
OpaqueObjects.forEach(o=>o.visible = true)
//绘制不透明物体
renderer.setClearColor(0, 1);
OpaqueObjects.forEach((o)=>{
o.visible = true
})
renderer.render(scene, camera, opaqueTarget, true);
OpaqueObjects.forEach(o=>o.visible = false)
//-----------
renderer.setClearColor(0, 1);
globalPeelUniforms.uOpaqueDepth.value = opaqueTarget.depthTexture;
TransparencyObjects.forEach((o)=>{
o.material = materialColor
o.visible = true
)
renderer.render(scene, camera, colorTarget, true);
TransparencyObjects.forEach((o)=>{
o.material = materialAlpha
})
renderer.render(scene, camera, alphaTarget, true);
//加权求平均,最终与不透明的像素进行混合
pmaterial.uniforms.uAccumulate.value = colorTarget.texture;
pmaterial.uniforms.uAccumulateAlpha.value = alphaTarget.texture;
pmaterial.uniforms.uOpaque.value = opaqueTarget.texture;
renderer.render(composScene, ocamera);