如何在three.js中正确实现Cook-Torrance着色?

时间:2014-06-07 10:02:40

标签: javascript three.js fragment-shader

我试图在三个实现Cook-Torrance着色算法。我有一个主要工作的解决方案,但它没有显示环境光的影响。没有被光照射的立方体的侧面是完全黑色的。如果我删除" Beckmann术语"那我确实可以看到环境光效果:

Image of the cube with completely back faces when not illuminated

同时,将Beckmann替换为始终返回0.0的函数:

Image of the cube with ambient term added to non-illuminated faces.


似乎错误行为的原因是分裂:

vec3 Specular = (Beckmann(NdotH) * G(NdotH, NdotV, VdotH, NdotL) * R_F(VdotH)) / ( NdotL* NdotV);

如果我将NdotL * NdotV修改为NdotV并将gl_FragColor的计算更改为:

gl_FragColor = vec4(beta * NdotL * (1.0-s)*Kd + beta * s*Specular + ambient*Kd, 1.0);

一切似乎都能正常运作。

我不明白的是:为什么?在任何地方都没有提到这个划分的问题,而且我不能100%确定即使剩下的部门也不会在其他情况下引起问题。


这里是完整的MWE:

<html>
    <head>
        <title>Cook-Torrance BRDF computed by shader</title>
        <style>

        body {
            font-family: Monospace;
            background-color: #f0f0f0;
            margin: 0px;
            overflow: hidden;
        }

        canvas {
            width: 100%;
            height: 100%;
        }

    </style>
        <script src="lib/three.min.js"></script>
        <script src="lib/OrbitControls.js"></script>
    </head>
    <body>

        <script type="text/x-glsl" id="vertex">
        varying vec3 transformedNormal;
        varying vec3 pointPosition;
        varying vec3 lightVector;
        uniform vec3 pointLightPosition;

        void main()
        {
            transformedNormal = normalMatrix * normal;
            pointPosition = (modelViewMatrix * vec4( position, 1.0 )).xyz;
            vec4 lPosition = viewMatrix * vec4( pointLightPosition, 1.0 );
            lightVector = lPosition.xyz - pointPosition;
            gl_Position = projectionMatrix * vec4(pointPosition,1.0);
        }
        </script>

        <script type="text/x-glsl" id="ct-fragment">
            uniform vec3 lightPower;
            uniform vec3 ambient;
            uniform vec3 Kd; // surface diffuse color
            uniform vec3 Ks; // surface specular color: equal to R_F(0)
            uniform float m; // material roughness (average slope of microfacets)
            uniform float s; // percentage of incoming light which is specularly reflected

            varying vec3 transformedNormal;
            varying vec3 pointPosition;
            varying vec3 lightVector;

            #define PI 3.14159265

            float G(float NdotH, float NdotV, float VdotH, float NdotL)
            {
                float G1 = 2.0 * NdotH * NdotV / VdotH;
                float G2 = 2.0 * NdotH * NdotL / VdotH;
                return min( 1.0, min( G1, G2 ));
            }

            vec3 R_F(float VdotH) {
                return Ks + (1.0 - Ks)*pow(1.0-VdotH, 5.0);
            }

            float Beckmann(float NdotH){
                float A = 1.0 / (pow(m,2.0)+pow(NdotH,4.0)*PI);
                float B = exp( - pow( tan(acos(NdotH)) , 2.0) / pow(m,2.0));
                return A*B;
            }

            void main()
            {
                vec3  n                 = normalize( transformedNormal );
                vec3  v                 = normalize( -pointPosition );
                vec3  l                 = normalize(  lightVector );
                vec3  h                 = normalize( v+l );
                float  NdotH            = max(0.0, dot( n, h ));
                float  VdotH            = max(0.0, dot( v, h ));
                float  NdotV            = max(0.0, dot( n, v ));
                float  NdotL            = max(0.0, dot( n, l ));
                // specular BRDF
                vec3 Specular = (Beckmann(NdotH) * G(NdotH, NdotV, VdotH, NdotL) * R_F(VdotH)) / ( NdotL* NdotV);
                vec3 beta = lightPower / ( 4.0  * PI * pow( length(lightVector),2.0) );
                gl_FragColor = vec4(beta * NdotL * ((1.0-s)*Kd + s*Specular) + ambient*Kd, 1.0);
            }
        </script>


        <script>
            var scene = new THREE.Scene();
            var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
            camera.position = new THREE.Vector3(0,0,5);

            var renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize( window.innerWidth, window.innerHeight );
            renderer.setClearColor( 0xf0f0f0 );
            document.body.appendChild( renderer.domElement );

            controls = new THREE.OrbitControls(camera, renderer.domElement);
            controls.target.set(0, 0, 0);

            var uniforms = {
                        Ks: { type: "v3", value: new THREE.Vector3() },
                        Kd: { type: "v3", value: new THREE.Vector3() },
                        ambient:    { type: "v3", value: new THREE.Vector3() },
                        pointLightPosition: { type: "v3", value: new THREE.Vector3() },
                        lightPower: { type: "v3", value: new THREE.Vector3() },
                        s: {type: "f", value: 0},
                        m: {type: "f", value: 0}
                    };

            var vs = document.getElementById("vertex").textContent;
            var fs = document.getElementById("ct-fragment").textContent;

            var material = new THREE.ShaderMaterial({ uniforms: uniforms, vertexShader: vs, fragmentShader: fs });

            var geometry = new THREE.CubeGeometry(1, 1, 1);
            var mesh = new THREE.Mesh(geometry, material);
            scene.add(mesh);

            light = new THREE.Mesh( new THREE.SphereGeometry( 1, 16, 16), new THREE.MeshBasicMaterial ({color: 0xffff00, wireframe:true}));
            light.position = new THREE.Vector3( 10.0, 10.0, 10.0 );
            scene.add( light );

            uniforms.Ks.value = new THREE.Vector3( 0.95, 0.93, 0.88 );
            uniforms.Kd.value = (new THREE.Vector3( 0.50754, 0.50754, 0.50754 ));
            uniforms.ambient.value = (new THREE.Vector3( 0.5, 0.5, 0.5 ));
            uniforms.pointLightPosition.value = new THREE.Vector3(light.position.x, light.position.y, light.position.z);
            uniforms.lightPower.value = new THREE.Vector3( 7000.0, 7000.0, 7000.0 );
            uniforms.s.value = 0.5;
            uniforms.m.value = 0.1;

            function animate() {

                requestAnimationFrame( animate );
                render();

            }

            function render() {
                controls.update();
                renderer.render(scene, camera);
            }

            animate();
        </script>
    </body>
</html>

1 个答案:

答案 0 :(得分:1)

阴影方程是Cook-Torrance着色模型的数学描述。 编写一个实际的着色器是一个不同的事情,应该考虑到浮点数之间的所有操作都不具有与等式中真实数学运算相同的属性。

在这种情况下,0潜水会导致问题。事实上,问题是Specular的定义是0潜水,但在分配到gl_FragColor时,我再次乘以NdotL获得0 * inf = NaN似乎NaN被GPU解释为零/负数(因此显示为黑色)。


作为参考,正确的main()是:

void main()
{
    vec3  n = normalize( transformedNormal );
    vec3  v = normalize( -pointPosition );
    vec3  l = normalize(  lightVector );
    vec3  h = normalize( v+l );

    vec3 specular = vec(0.0, 0.0, 0.0);           
    float  NdotH = max(0.0, dot( n, h ));
    float  VdotH = max(0.0, dot( v, h ));
    float  NdotV = max(0.0, dot( n, v ));
    float  NdotL = max(0.0, dot( n, l ));
    if (NdotL > 0 && NdotV > 0) 
    {
        specular = (Beckmann(NdotH) * G(NdotH, NdotV, VdotH, NdotL) * R_F(VdotH)) / ( NdotL* NdotV);
    }
    vec3 beta = lightPower / ( 4.0  * PI * pow( length(lightVector),2.0) );
    gl_FragColor = vec4(beta * NdotL * ((1.0-s)*Kd + s*specular) + ambient*Kd, 1.0);
}