2022-10-25---------------阴影贴图

80 阅读3分钟

一、原理

实现阴影的基本思想是:太阳看不见阴影。如果在光源处放置以为观察者,其视线方向与光线一致,那么观察者也看不到阴影。他看到的每一处都在光的照射下,而那些背后的,他没有看到的物体则处在阴影中。这里,我们需要用到光源与物体之间的距离(实际上也就是物体在光源坐标系下的深度z值)来决定物体是否可见。如图所示,同一条光线上有两个点P1和P2,由于P2的z值大于P1,所以P2在阴影中。

image.png

我们需要使用两对着色器以实现阴影:
[1]一对着色器用来计算光源到物体的距离,
[2]另一对着色器根据[1]中计算出的距离绘制场景。
使用一张纹理图像把[1]的结果传入[2]中,这张纹理图像就被称为阴影贴图(shadow map),而通过阴影贴图实现阴影的方法就被称为阴影映射(shadow mapping)。阴影映射的过程包括以下两步:

  1. 将视点移动到光源的位置处,并运行[1]中的着色器。这时,那些“将要被绘出”的片元都是被光照射到的,即落在这个像素上的各个片元中最前面的。我们并不实际地绘制出片元的颜色,而是将片元的z值写入到阴影贴图中。

  2. 将视点移回原来的位置,运行[2]中的着色器绘制场景。此时,我们计算出每个片元在光源坐标系(即[1]中的视点坐标系)下的坐标,并与阴影贴图中记录的z值比较,如果前者大于后者,就说明当前片元处在阴影之中,用较深暗的颜色绘制。

二、效果

image.png

三、代码



    var v_shader =/*glsl*/`
      attribute vec4 a_Position;
      attribute vec2 a_Uv;
      attribute vec4 a_Color;
      uniform mat4 u_ModelMatrix; 
      uniform mat4 u_ViewMatrix; 
      uniform mat4 u_ProjectMatrix; 
      uniform mat4 u_LightViewMatrix;
      varying vec2 v_Uv;
      varying vec4  v_LightPosition;
      varying vec4 v_Color;
      void main(){
        gl_Position=u_ProjectMatrix*u_ViewMatrix*u_ModelMatrix*a_Position;
        //计算光源坐标系下位置
        v_LightPosition=u_ProjectMatrix*u_LightViewMatrix*u_ModelMatrix*a_Position;
        v_Uv=a_Uv;
        v_Color=a_Color;
      }
    `

    var f_shader =/*glsl*/`
     #ifdef GL_ES
      precision highp float;
      #endif
        varying vec2 v_Uv;
        varying vec4  v_LightPosition;
        uniform sampler2D u_Texture;
        varying vec4 v_Color;


        float unPackDepth(const in vec4 rgbaDepth){
            const vec4 bitShift=vec4(1.0,1.0/256.0,1.0/(256.0*256.0),1.0/(256.0*256.0*256.0));
            float depth=dot(rgbaDepth,bitShift);
            return depth;
        }

        void main(){

            //计算光源坐标系下ndc坐标
            vec3 shadowCoord=(v_LightPosition.xyz/v_LightPosition.w)/2.0+0.5;
           
            //从阴影贴图中读取原来深度
            vec4 rgbaDepth=texture2D(u_Texture,shadowCoord.xy);
             //获取深度
            float depth=unPackDepth(rgbaDepth);

            //比较深度
            float visibility=(shadowCoord.z>depth+0.0015)?0.7:1.0;


            gl_FragColor=vec4(vec3(1.0)*visibility,v_Color.a);

        }

    `

    var fbo_v_shader =/*glsl*/`
       attribute vec4 a_Position;
       attribute vec4 a_Color;

       uniform mat4 u_ModelMatrix; 
      uniform mat4 u_ViewMatrix; 
      uniform mat4 u_ProjectMatrix; 

      varying vec4 v_Color;

      void main(){
        gl_Position=u_ProjectMatrix*u_ViewMatrix*u_ModelMatrix*a_Position;
        v_Color=a_Color;
      }
    `

    var fbo_f_shader =/*glsl*/`
     #ifdef GL_ES
      precision mediump float;
       
      #endif
      varying vec4 v_Color;
        void main(){
            const vec4 bitShift=vec4(1.0,256.0,256.0*256.0,256.0*256.0*256.0);
            const vec4 bitMask=vec4(1.0/256.0,1.0/256.0,1.0/256.0,0.0);
            vec4 rgbaDepth=fract(gl_FragCoord.z*bitShift);
            rgbaDepth-=rgbaDepth.gbaa*bitMask;
            gl_FragColor=rgbaDepth;
        }

    `






    //声明js需要的相关变量
    var canvas = document.getElementById("canvas");
    var gl = getWebGLContext(canvas);
    var fbo_program, normal_program
    async function main() {
        if (!gl) {
            console.log("你的浏览器不支持WebGL");
            return;
        }



        fbo_program = createProgram(gl, fbo_v_shader, fbo_f_shader)
        if (!fbo_program) {
            console.warn("创建FBO程序失败!");
            return;
        }

        normal_program = createProgram(gl, v_shader, f_shader)
        if (!normal_program) {
            console.warn("创建正常程序失败!");
            return;
        }



        //设置透视投影矩阵
        var projMatrix = new Matrix4();
        projMatrix.setPerspective(30, canvas.width / canvas.height, 0.1, 100);

        //设置视角矩阵的相关信息(视点,视线,上方向)
        var viewMatrix = new Matrix4();
        viewMatrix.setLookAt(0, 4, 6, 0, 0, 0, 0, 1, 0)
        //开启隐藏面清除
        gl.enable(gl.DEPTH_TEST);
        // gl.enable(gl.CULL_FACE)

        gl.useProgram(fbo_program)
        gl.program = fbo_program

        //获取内置变量的信息
        getVariableLocation();
        const fbo_box = createCube(gl);
        const fbo_plane = createPlane(gl)


        gl.useProgram(normal_program)
        gl.program = normal_program

        //获取内置变量的信息
        getVariableLocation();


        const box = createCube(gl);
        const plane = createPlane(gl)



        const offset_width = 1024, offset_height = 1024

        //创建FBO
        var fboTexture = createFBOTexture(gl, offset_width, offset_height)
        //初始化
        const fbo = createFrameBuffer(gl, fboTexture, offset_width, offset_height)

        //获取光源mvp矩阵
        const u_LightViewMatrix = gl.getUniformLocation(gl.program, 'u_LightViewMatrix')


        document.addEventListener("keydown", (e) => {

            if (e.key === "ArrowUp") {
                lightPosition_y += 0.01
            } else if (e.key === "ArrowDown") {
                lightPosition_y -= 0.01
            } else if (e.key === "ArrowLeft") {
                lightPosition_x -= 0.1
            } else if (e.key === "ArrowRight") {
                lightPosition_x += 0.1
            }


        });




        // await draw(projMatrix,viewMatrix)
        //设置底色

        //根据时间绘制
        var tick = async function () {

            await draw(projMatrix, viewMatrix, box, plane, fbo, fbo_box, fbo_plane, u_LightViewMatrix)

            //重复请求
            requestAnimationFrame(tick)
        }
        tick()




    }

    let lightViewMatrix = new Matrix4()
    var lightPosition_x = 1.01, lightPosition_y = 5.2, lightPosition_z = 0.0
    async function draw(projMatrix, viewMatrix, box, plane, fbo, fbo_box, fbo_plane, u_LightViewMatrix) {
        //清空颜色和深度缓冲区
        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        gl.viewport(0, 0, canvas.width, canvas.height);
        //设置视点在光源处
        lightViewMatrix.setLookAt(lightPosition_x, lightPosition_y, lightPosition_z, 0, 0, 0, 0, 1, 0)
        gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)

        gl.useProgram(fbo_program)
        gl.program = fbo_program


        await drawBox(projMatrix, lightViewMatrix, fbo_box)
        await drawPlane(projMatrix, lightViewMatrix, fbo_plane)

        gl.bindFramebuffer(gl.FRAMEBUFFER, null)

        gl.useProgram(normal_program)
        gl.program = normal_program

        gl.activeTexture(gl.TEXTURE0);
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 0);
        gl.bindTexture(gl.TEXTURE_2D, fbo.texture);
        gl.uniform1i(normal_program.texture, 0);

        //给光源view矩阵赋值
        gl.uniformMatrix4fv(u_LightViewMatrix, false, lightViewMatrix.elements)
        await drawBox(projMatrix, viewMatrix, box)
        await drawPlane(projMatrix, viewMatrix, plane)
    }







    async function drawBox(projMatrix, viewMatrix, box) {
        const program = gl.program;



        //设置模型矩阵的相关信息
        var modelMatrix = new Matrix4();
        modelMatrix.setScale(0.3, 0.3, 0.3)
        modelMatrix.translate(0, 1.0, 0)
        gl.uniformMatrix4fv(program.modelMatrix, false, modelMatrix.elements);
        gl.uniformMatrix4fv(program.viewMatrix, false, viewMatrix.elements);
        gl.uniformMatrix4fv(program.projectMatrix, false, projMatrix.elements);

        const arrayBuffers = box.arrayBuffers
        arrayBuffers.forEach(arrayBuffer => {
            if (arrayBuffer) {
                writeAttributeVariable(gl, arrayBuffer.attribute, arrayBuffer)
            }
        });




        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, box.indexBuffer);
        //绘制图形
        gl.drawElements(gl.TRIANGLES, box.length, gl.UNSIGNED_BYTE, 0);
    }


    async function drawPlane(projMatrix, viewMatrix, plane) {
        const program = gl.program;



        //设置模型矩阵的相关信息
        var modelMatrix = new Matrix4();
        modelMatrix.setScale(1, 1, 1)
        modelMatrix.translate(0, -0.5, 0)
        gl.uniformMatrix4fv(program.modelMatrix, false, modelMatrix.elements);
        gl.uniformMatrix4fv(program.viewMatrix, false, viewMatrix.elements);
        gl.uniformMatrix4fv(program.projectMatrix, false, projMatrix.elements);

        const arrayBuffers = plane.arrayBuffers
        arrayBuffers.forEach(arrayBuffer => {
            if (arrayBuffer) {
                writeAttributeVariable(gl, arrayBuffer.attribute, arrayBuffer)
            }
        });


        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, plane.indexBuffer);
        //绘制图形
        gl.drawElements(gl.TRIANGLES, plane.length, gl.UNSIGNED_BYTE, 0);
    }