2022-10-17-------------------------环境映射

182 阅读3分钟

一、原理

1.1 、反射

反射这个属性表现为物体(或物体的一部分)反射它周围环境,即根据观察者的视角,物体的颜色或多或少等于它的环境。镜子就是一个反射性物体:它会根据观察者的视角反射它周围的环境。

反射的原理并不难。下面这张图展示了我们如何计算反射向量,并如何使用这个向量来从立方体贴图中采样:

image.png

    //计算反射颜色
    vec3 reflectDir=reflect(viewDir,normalize(v_Normal));
    vec3 reflectColor=textureCube(u_CubeMapTexture,reflectDir).rgb;

1.2、折射

环境映射的另一种形式是折射,它和反射很相似。折射是光线由于传播介质的改变而产生的方向变化。在常见的类水表面上所产生的现象就是折射,光线不是直直地传播,而是弯曲了一点。将你的半只胳膊伸进水里,观察出来的就是这种效果。

折射是通过斯涅尔定律(Snell’s Law)来描述的,使用环境贴图的话看起来像是这样: image.png 同样,我们有一个观察向量,一个法向量,而这次是折射向量。可以看到,观察向量的方向轻微弯曲了。弯折后的向量将会用来从立方体贴图中采样。

折射可以使用GLSL的内建refract函数来轻松实现,它需要一个法向量、一个观察方向和两个材质之间的折射率(Refractive Index)。

//计算折射颜色
vec3 refractDir=refract(viewDir,normalize(v_Normal),0.75);
vec3 refractColor=textureCube(u_CubeMapTexture,refractDir).rgb;

二、效果

20221017_215931.gif

三、代码

 //顶点着色器
    var skyBox_VS = /*glsl*/ `
      attribute vec4 a_Position;
      uniform mat4 u_ModelMatrix; 
      uniform mat4 u_ViewMatrix; 
      uniform mat4 u_ProjectMatrix; 
    
      varying vec3 v_Position;
      void main(){
        mat4 relativeViewMatrix=u_ViewMatrix;
        relativeViewMatrix[3].x=0.0; 
        relativeViewMatrix[3].y=0.0; 
        relativeViewMatrix[3].z=0.0; 
        vec4 clipPosition=u_ProjectMatrix*relativeViewMatrix*u_ModelMatrix * a_Position;
      
    
         gl_Position =clipPosition;
         v_Position=a_Position.rgb;

      }`;

    //片元着色器
    var skyBox_FS = /*glsl*/ `
      #ifdef GL_ES
      precision mediump float;
      #endif
     
      uniform samplerCube u_CubeMapTexture;
       uniform vec3 u_AmbitionColor;
      varying vec3 v_Position;


      void main(){
        gl_FragColor=textureCube(u_CubeMapTexture, v_Position);

      }`;

    var v_shader =/*glsl*/`
    attribute vec4 a_Position;
        attribute vec2 a_Uv;
        attribute vec3 a_Normal;

       uniform mat4 u_ModelMatrix; 
      uniform mat4 u_ViewMatrix; 
      uniform mat4 u_ProjectMatrix; 
      uniform mat4 u_NormalMatrix;
      varying vec2 v_Uv;
      varying vec3 v_Normal;
      varying vec3 v_Position;
      void main(){
        gl_Position=u_ProjectMatrix*u_ViewMatrix*u_ModelMatrix*a_Position;
        v_Uv=a_Uv;
        v_Normal=(u_NormalMatrix*vec4(a_Normal,1.0)).xyz;
        v_Position=(u_ModelMatrix*a_Position).xyz;
      }
    `

    var f_shader =/*glsl*/`
     #ifdef GL_ES
      precision mediump float;
      #endif
        varying vec2 v_Uv;
         varying vec3 v_Normal;
         varying vec3 v_Position;
        uniform sampler2D u_Texture;
        uniform vec3 u_ViewPosition;
         uniform samplerCube u_CubeMapTexture;
    
        void main(){
            //计算反射向量
            vec3 viewDir=normalize(v_Position-u_ViewPosition);

            //计算反射颜色
            vec3 reflectDir=reflect(viewDir,normalize(v_Normal));
            vec3 reflectColor=textureCube(u_CubeMapTexture,reflectDir).rgb;

            //计算折射颜色
            vec3 refractDir=refract(viewDir,normalize(v_Normal),0.75);
            vec3 refractColor=textureCube(u_CubeMapTexture,refractDir).rgb;


            vec4 baseColor=vec4(0.0,0.0,0.8,0.8);
            // gl_FragColor=vec4(refractColor,baseColor.a);
            gl_FragColor=vec4(mix(baseColor.rgb,reflectColor+refractColor,0.7),baseColor.a);
        }

    `


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

        skyboxProgram = createProgram(gl, skyBox_VS, skyBox_FS);
        if (!skyboxProgram) {
            console.warn("创建程序失败!");
            return;
        }

        boxProgram = createProgram(gl, v_shader, f_shader)
        if (!boxProgram) {
            console.warn("创建立方体程序失败!");
            return;
        }



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

        //设置视角矩阵的相关信息(视点,视线,上方向)
        var viewMatrix = new Matrix4();
        viewMatrix.setLookAt(0, 2, 4, 0, 0, 0, 0, 1, 0);

        planeModelMatrix = new Matrix4()



        //开启隐藏面清除
        gl.enable(gl.DEPTH_TEST);
        // gl.enable(gl.CULL_FACE)



        gl.program = skyboxProgram;
        gl.useProgram(skyboxProgram);
        //获取内置变量的信息
        getVariableLocation();

        const cube = createCube(gl);


        await initCubeTexture(
            [
                "./image/skybox/right.jpg",
                "./image/skybox/left.jpg",
                "./image/skybox/top.jpg",
                "./image/skybox/bottom.jpg",

                "./image/skybox/front.jpg",
                "./image/skybox/back.jpg",
            ]
        )



        gl.useProgram(boxProgram)
        gl.program = boxProgram

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

        //初始化纹理
        await initTexture(gl, "./image/box.jpg", 1);





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


        // gl.depthFunc(gl.LEQUAL);
        //根据时间绘制
        var tick = function () {

            draw(projMatrix, viewMatrix, cube, plane)

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

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

            if (e.key === "ArrowUp") {
                planeModelMatrix.rotate(1, 1, 0, 0)
            } else if (e.key === "ArrowDown") {
                planeModelMatrix.rotate(-1, 1, 0, 0)
            } else if (e.key === "ArrowLeft") {
                planeModelMatrix.rotate(-1, 0, 1, 0)
            } else if (e.key === "ArrowRight") {
                planeModelMatrix.rotate(1, 0, 1, 0)
            }


        });




    }

    function draw(projMatrix, viewMatrix, cube, plane) {
        //清空颜色和深度缓冲区
        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        gl.depthMask(false);
        gl.useProgram(skyboxProgram)
        gl.program = skyboxProgram


        //绘制三角形
        drawSkyBox(projMatrix, viewMatrix, cube);
        gl.depthMask(true);
        gl.useProgram(boxProgram)
        gl.program = boxProgram


        drawBox(projMatrix, viewMatrix, plane)


    }





    function drawSkyBox(projMatrix, viewMatrix, cube) {
        const program = gl.program;
        //设置视角矩阵的相关信息(视点,视线,上方向)






        //将试图矩阵传给u_ViewMatrix变量
        gl.uniformMatrix4fv(program.projectMatrix, false, projMatrix.elements);


        //设置模型矩阵的相关信息
        var modelMatrix = new Matrix4();
        gl.uniformMatrix4fv(program.modelMatrix, false, modelMatrix.elements);


        gl.uniformMatrix4fv(program.viewMatrix, false, viewMatrix.elements);


        const arrayBuffers = cube.arrayBuffers

        //写入缓冲区
        arrayBuffers.forEach(arrayBuffer => {
            if (arrayBuffer) {
                writeAttributeVariable(gl, arrayBuffer.attribute, arrayBuffer)
            }
        });


        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cube.indexBuffer);

        //绘制图形
        gl.drawElements(gl.TRIANGLES, cube.length, gl.UNSIGNED_BYTE, 0);

        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
    }


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



        //设置模型矩阵的相关信息
        // var modelMatrix = new Matrix4();
        gl.uniformMatrix4fv(program.modelMatrix, false, planeModelMatrix.elements);


        gl.uniformMatrix4fv(program.viewMatrix, false, viewMatrix.elements);


        //设置透视投影矩阵
        var projMatrix = new Matrix4();
        projMatrix.setPerspective(30, canvas.width / canvas.height, 0.1, 100);
        //将试图矩阵传给u_ViewMatrix变量
        gl.uniformMatrix4fv(program.projectMatrix, false, projMatrix.elements);


        //传入眼睛位置
        const viewPosition = gl.getUniformLocation(program, 'u_ViewPosition')

        gl.uniform3fv(viewPosition, [0, 2, 4]);


        var normalMatrix = new Matrix4()

        normalMatrix.transpose(normalMatrix.setInverseOf(planeModelMatrix))
        gl.uniformMatrix4fv(program.normalMatrix, false, normalMatrix.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);
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
    }