TeaPot 用webgl画茶壶(2) Phong Shading

时间:2022-02-10 05:09:26

在Fragment Shader里应用Phong Shading使得茶壶更逼真。即使是单一颜色的茶壶,只要光源的位置变化,或眼睛的位置变化,看到的茶壶的各个部分的颜色明暗是不一定一样的。所谓Phong Shading就是Phong这个人提出的一种决定每个像素颜色的方法。

总的来说,我们希望看到怎样的茶壶?我们希望它跟真的一样,有光照着它,所以照到的地方亮,照不到的地方暗,迎着光的地方还有高亮反射光。

点e看到点p有颜色,是因为有光子从p跑到了e。点p有光子,除非p是光源,不然光子肯定是从别的地方跑来。这个别的地方之一,就是光源,有光源照着p。但是,如果没有光源的直射,比如拉上窗帘的房间里,还是可以看到p,因为p点周围无数个点(茶壶上其它部分,杯子,桌子等等)都往它发射着光子。这是别的地方之二,环境。

 

总结一下一个点p(不是光源),被在e点看到了,说明

1. 有光子从别的地方跑到了p。

  两个可能的来处,一是光源,一是环境。

2.p点的光子又跑到了e

到达p的光子有三个可能,一是被吸收产生热量,二是打入内部又散射出来,三是直接从表面反射出去。被吸收的就不用考虑了,剩下的一部分散射,一部分反射,散射是指这一部分光子从p出来后往各个方向跑(当然还是沿着直线),有一个方向指向e,e会看到散射光。散射光往往代表p本来的颜色,反射光与p本来的颜色几乎无关,因为它没有进入p内部而是在p处改变了方向,它还是原来的光。因为环境给p的光子是从各个方向来的,不好分析它们的反射方向,数量又不大,所以一般认为从环境里到达p的光子全都散射出去了。

 

环境里到达p的光子强度是固定的,而且它到达茶壶其它p1,p2等等的光子强度一样。而同一个光源,给茶壶各个p,p1,p2的光子强度却不是固定的。这个强度的大小与入射角度有关。Lambertian表示可以用  cos(p的法向量n与入射光的反方向向量l的夹角)*L的强度  来代表光源L到达p的强度,也就是n=normalize(n),l=normalize(l),dot(n,l)*L的强度。dot(n,l)可能小于0,小于0代表光是从p背面照着它,所以没有光子跑到p,max(0, dot(n,l))*L的强度。

光源的反射部分的方向是可以算出的,是向量r。e-p与r的夹角越小,说明接收的反射的越多。也可以看,向量h与n的夹角,h=normalize(e-p+l)。

p点的颜色=环境光的强度与颜色*散射系数+光源强度与颜色*max(0, dot(n,l))*散射系数+光源强度与颜色*pow(dot(h,n),P)

散射系数与p本身颜色紧密相关。pow是为了快速缩小高亮大小,否则高亮不真。P是光滑系数。

pow(dot(h,n),P)是改良的Phong shading。Phong提出的是pow(max(0,dot(r,e-p)),P)。区别是当e-p与r是钝角时,改良后的Phong Shading依然会产生反射光。光滑的表面放大后依然粗糙,有面向各个方向的Microfacets。

http://opengl.datenwolf.net/gltut/html/Illumination/Tutorial%2011.html

<html>
    <head>
        <title>TeaPolt</title>
    </head>
    
    <body onload="main()">
        <canvas id="viewPort" width="600" height="800">
            This browser do not support webgl.
        </canvas>
    
    <script src="./examples/lib/cuon-matrix.js"></script>
    <script src="./TeaPotData.js"></script>
    <script>
function main()
{
    //alert("bb");
    //get webgl context
    var viewPort = document.getElementById("viewPort");
    var gl = viewPort.getContext("webgl") || viewPort.getContext("experimental-webgl");
    
    var VERTEX_SHADER =
    "attribute vec4 a_Position;\n" +
    "attribute vec3 a_VNomal;\n" +
    "varying vec4 v_Position;\n" +
    "varying vec3 v_VNomal;\n" +
    "uniform mat4 u_ModelMatrix;\n" +
    "uniform mat4 u_ViewMatrix;\n" +
    "uniform mat4 u_ProjMatrix;\n" +
    "void main()\n" +
    "{\n" +
    " gl_Position = u_ProjMatrix*u_ViewMatrix*u_ModelMatrix*a_Position;\n" +
    " v_Position = a_Position;\n" +
    " v_VNomal = a_VNomal;\n" +
    "}\n"; //光线向量l不是线性插值的,必须在FragmentShader里算,所以每一个Fragment要带它所对应的vertex在空间里的位置(位置是可以插值的)。用着个位置和光源位置来算l。
    
    var FRAGMENT_SHADER = 
    "#ifdef GL_ES\n" +
    "precision mediump float;\n" +
    "#endif\n" +
    "uniform vec3 u_EyePosition;\n" +
    "uniform vec3 u_pointLightPosition;\n" +
    "varying vec4 v_Position;\n" +
    "varying vec3 v_VNomal;\n" +
    "void main()\n" +
    "{\n" +
    "   vec4 pointLightColor = vec4(1.0, 1.0, 1.0, 1.0);\n" +
    "   vec4 pointlightStrength = vec4(1.0, 1.0, 1.0, 1.0);\n" +
    "   vec4 envLightColor = vec4(1.0, 1.0, 1.0, 1.0);\n" +
    "   vec4 envlightStrength = vec4(0.3, 0.3, 0.3, 1.0);\n" +
    "   vec4 matrilColor = vec4(1.0, 1.0, 1.0, 1.0);\n" +
    "   float p = 6.0;\n" +
    "   vec3 l = normalize(vec3(u_pointLightPosition.x - v_Position.x, u_pointLightPosition.y - v_Position.y, u_pointLightPosition.z - v_Position.z));\n" +
    "   vec3 e = normalize(vec3(u_EyePosition.x - v_Position.x, u_EyePosition.y - v_Position.y, u_EyePosition.z - v_Position.z));\n" +
    "   vec3 n = v_VNomal;\n" +
    "   n = normalize(n);\n" +
    "   float nl = dot(n, l);\n" +
    "   if(nl<0.0) nl=0.0;\n" +
    "   vec3 h = normalize(e+l);\n" +
    "   float hn = dot(h, n);\n" +
    "   gl_FragColor = matrilColor*envLightColor*envlightStrength+matrilColor*pointLightColor*pointlightStrength*nl+pointLightColor*pointlightStrength*pow(hn,p);\n" +
    "}\n";
    /*var VERTEX_SHADER =
    "attribute vec4 a_Position;\n" +
    "uniform mat4 u_ModelMatrix;\n" +
    "uniform mat4 u_ViewMatrix;\n" +
    "uniform mat4 u_ProjMatrix;\n" +
    "void main()\n" +
    "{\n" +
    " gl_Position = u_ProjMatrix*u_ViewMatrix*u_ModelMatrix*a_Position;\n" +
    "}\n";
    
    var FRAGMENT_SHADER = 
    "#ifdef GL_ES\n" +
    "precision mediump float;\n" +
    "#endif\n" +
    "void main()\n" +
    "{\n" +
    "   gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n" +
    "}\n";*/
    var program = createProgram(gl, VERTEX_SHADER, FRAGMENT_SHADER);
    if(!program)
    {
        return;
    }
    gl.useProgram(program);
    gl.program = program;
    
    var a_Position, u_ModelMatrix, u_ViewMatrix, u_ProjMatrix, a_VNomal, u_EyePosition, u_pointLightPosition;
    a_Position = gl.getAttribLocation(gl.program, "a_Position");
    a_VNomal = gl.getAttribLocation(gl.program, "a_VNomal");
    u_ModelMatrix = gl.getUniformLocation(gl.program, "u_ModelMatrix");
    u_ViewMatrix = gl.getUniformLocation(gl.program, "u_ViewMatrix");
    u_ProjMatrix = gl.getUniformLocation(gl.program, "u_ProjMatrix");
    u_EyePosition = gl.getUniformLocation(gl.program, "u_EyePosition");
    u_pointLightPosition = gl.getUniformLocation(gl.program, "u_pointLightPosition");
    if(a_Position < 0 || a_VNomal < 0 || !u_ModelMatrix || !u_ViewMatrix || !u_ProjMatrix || !u_EyePosition || !u_pointLightPosition)
    {
        alert("Failed to get store location from progrom");
        return;
    }
    
   
    
    //The following code snippet creates a vertex buffer and binds the vertices to it  
    var teaPotvPropertiesData = gl.createBuffer();  
    gl.bindBuffer(gl.ARRAY_BUFFER, teaPotvPropertiesData); //alert("bb"+teaPotData);
    gl.bufferData(gl.ARRAY_BUFFER, teaPotData.vertexPositions, gl.STATIC_DRAW);
    var VFSIZE = teaPotData.vertexPositions.BYTES_PER_ELEMENTS;
    gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, VFSIZE * 3, VFSIZE * 0 );
    gl.enableVertexAttribArray(a_Position);
    
    var teaPotnPropertiesData = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, teaPotnPropertiesData);
    gl.bufferData(gl.ARRAY_BUFFER, teaPotData.vertexNormals, gl.STATIC_DRAW);
    var VNFSIZE = teaPotData.vertexNormals.BYTES_PER_ELEMENT;
    gl.vertexAttribPointer(a_VNomal, 3, gl.FLOAT, false, VNFSIZE * 3, VNFSIZE * 0);
    gl.enableVertexAttribArray(a_VNomal);
          
    //The following code snippet creates a vertex buffer and binds the indices to it  
    teaPotPropertiesIndex = gl.createBuffer();  
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, teaPotPropertiesIndex);  
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, teaPotData.indices, gl.STATIC_DRAW); 
    var IINDEX = teaPotData.indices.length;
    var IFSIZE = teaPotData.indices.BYTES_PER_ELEMENT;//new Uint16Array(indices)
    
    var eyePosition = new Float32Array([0.0, 0.0, 100.0]);
    var pointLightPosition = new Float32Array([0.0, 0.0, 50.0]);
    gl.uniform3fv(u_EyePosition, eyePosition);
    gl.uniform3fv(u_pointLightPosition, pointLightPosition);
    
    var modelMatrix = new Matrix4();
    var viewMatrix = new Matrix4();
    var projMatrix = new Matrix4();
    viewMatrix.setLookAt(eyePosition[0], eyePosition[1], eyePosition[2], 0.0, 0.0, 0.0, 0, 1, 0);
    projMatrix.setPerspective(30,viewPort.width/viewPort.height,1,100);
    //modelMatrix.setRotate(0, 0, 1, 0);
    modelMatrix.setIdentity();
    //viewMatrix.setIdentity();
    //projMatrix.setIdentity();
    
    gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);
    gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);
    gl.uniformMatrix4fv(u_ProjMatrix, false, projMatrix.elements);
    
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT || gl.DEPTH_BUFFER_BIT);
    //gl.drawArrays(gl.TRIANGLES, 0, n);
    gl.drawElements(gl.TRIANGLES, IINDEX, gl.UNSIGNED_SHORT, IFSIZE * 0);
}

        
function createProgram(gl, vShaderSource, fShaderSource)
{
    var vShader = createShader(gl, gl.VERTEX_SHADER, vShaderSource);
    var fShader = createShader(gl, gl.FRAGMENT_SHADER, fShaderSource);
    if(!vShader || !fShader)
    {
        return null;
    }
    var program = gl.createProgram();
    if(!program)
    {
        return null;
    }
    gl.attachShader(program, vShader);
    gl.attachShader(program, fShader);
    gl.linkProgram(program);
    var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
    if(!linked)
    {
        alert("Failed to link program " + gl.getProgramInfoLog(program));
        gl.deleteProgram(program);
        gl.deleteShader(fragmentShader);
        gl.deleteShader(vertexShader);
        return null;
    }
    return program;
    
    
}
        
function createShader(gl, type, source)
{
    var shader = gl.createShader(type);
    if(!shader)
    {
        alert("Failed to create "+ type + "shader");
        return null;
    }
    gl.shaderSource(shader, source);
    gl.compileShader(shader);
    var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
    if(!compiled)
    {
        alert("Failed to compile shader " + gl.getShaderInfoLog(shader));
        gl.deleteShader(shader);
        return null;
    }
    
    return shader;
}
    
    </script>

    </body>
</html>