Three.js OIT 透明渲染

Three.js OIT 透明渲染

为了正确显示半透明的模型,各路前辈们研究了很多方法。最常用的OIT方法,在Three.js 中没有提供相关的模块,经过几天的研究,结合两位大神 WebGL 九浅一深 和 Night_Aurora 的总结,在Three中做了简单的实现。

Three.js OIT 透明渲染_第1张图片

Three.js OIT 透明渲染_第2张图片

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);

你可能感兴趣的:(webgl,shader)