【视觉高级篇】23 # 如何模拟光照让3D场景更逼真?(上)

news2024/11/26 5:26:39

说明

【跟月影学可视化】学习笔记。

光照效果简介

物体的光照效果是由光源、介质(物体的材质)和反射类型决定的,而反射类型又由物体的材质特点决定。

在 3D 光照模型中,根据不同的光源特点分为四种:

  • 环境光(Ambient Light):指物体所在的三维空间中天然的光,它充满整个空间,在每一处的光照强度都一样。
    • 特点1:在空间中均匀分布,在任何位置上环境光的颜色都相同
    • 特点2:环境光没有方向,与物体的材质有关
  • 平行光(Directional Light):平行光是朝着某个方向照射的光,能够照亮几何体的一部分表面,它属于有向光。
  • 点光源(Positional Light):指空间中某一点发出的光,与方向光不同的是,点光源不仅有方向属性,还有位置属性。
  • 聚光灯(Spot Light):与点光源相比,聚光灯增加了方向以及角度范围,只有在这个范围内,光线才能照到。

点光源跟平行光的示意图:
在这里插入图片描述

聚光灯示意图:

在这里插入图片描述

有向光在与物体发生作用的时候,根据物体的材质特性,会产生两种反射类型:

  • 漫反射(Diffuse reflection)
  • 镜面反射(Specular reflection)

漫反射示意图:
在这里插入图片描述
一个物体最终的光照效果,是漫反射、镜面反射以及环境光叠加在一起的效果,示意图如下:

在这里插入图片描述

如何给物体增加环境光效果?

环境光没有方向,物体表面反射环境光的效果,只和环境光本身以及材质的反射率有关。

物体在环境光中呈现的颜色的公式如下:(环境光的颜色为 L,材质对光的反射率为 R。)

在这里插入图片描述

下面实现给物体增加环境光效果:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>如何给物体增加环境光效果</title>
        <style>
            canvas {
                border: 1px dashed #fa8072;
            }
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script type="module">
            import { Renderer, Camera, Transform, Sphere, Box, Cylinder, Torus, Orbit, Program, Mesh, Color } from './common/lib/ogl/index.mjs';
            // JavaScript Controller Library
            import * as dat from './common/lib/dat.gui.js';
            console.log(dat)

            const canvas = document.querySelector('canvas');
            const renderer = new Renderer({
                canvas,
                width: 512,
                height: 512,
            });

            const gl = renderer.gl;
            gl.clearColor(1, 1, 1, 1);
            const camera = new Camera(gl, {fov: 35});
            camera.position.set(0, 0, 10);
            camera.lookAt([0, 0, 0]);

            const scene = new Transform();

            const vertex = `
                precision highp float;

                attribute vec3 position;
                attribute vec3 normal;

                uniform mat4 modelViewMatrix;
                uniform mat4 projectionMatrix;
                uniform mat3 normalMatrix;

                void main() {
                    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                }
            `;

            // 传入环境光 ambientLight 和材质反射率 materialReflection
            const fragment = `
                precision highp float;

                uniform vec3 ambientLight;
                uniform vec3 materialReflection;

                void main() {
                    gl_FragColor.rgb = ambientLight * materialReflection;
                    gl_FragColor.a = 1.0;
                }
            `;

            // 创建四个不同的几何体,初始化它们的环境光 ambientLight 以及材质反射率 materialReflection
            const sphereGeometry = new Sphere(gl);
            const cubeGeometry = new Box(gl);
            const cylinderGeometry = new Cylinder(gl);
            const torusGeometry = new Torus(gl);

            const program1 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ambientLight: {value: [1, 1, 1]},
                    materialReflection: {value: [250/255, 128/255, 114/255]},
                },
            });
            const program2 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ambientLight: {value: [1, 1, 1]},
                    materialReflection: {value: [218/255, 165/255, 32/255]},
                },
            });
            const program3 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ambientLight: {value: [1, 1, 1]},
                    materialReflection: {value: [46/255, 139/255, 87/255]},
                },
            });
            const program4 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ambientLight: {value: [1, 1, 1]},
                    materialReflection: {value: [106/255, 90/255, 205/255]},
                },
            });

            const torus = new Mesh(gl, {geometry: torusGeometry, program: program1});
            torus.position.set(0, 1.3, 0);
            torus.setParent(scene);

            const sphere = new Mesh(gl, {geometry: sphereGeometry, program: program2});
            sphere.position.set(1.3, 0, 0);
            sphere.setParent(scene);

            const cube = new Mesh(gl, {geometry: cubeGeometry, program: program3});
            cube.position.set(0, -1.3, 0);
            cube.setParent(scene);

            const cylinder = new Mesh(gl, {geometry: cylinderGeometry, program: program4});
            cylinder.position.set(-1.3, 0, 0);
            cylinder.setParent(scene);

            const controls = new Orbit(camera);

            // 添加动画
            requestAnimationFrame(update);
            function update() {
                requestAnimationFrame(update);
                controls.update();

                torus.rotation.y -= 0.02;
                sphere.rotation.y -= 0.03;
                cube.rotation.y -= 0.04;
                cylinder.rotation.y -= 0.02;

                renderer.render({scene, camera});
            }

            // 添加控制
            const gui = new dat.GUI();
            const palette = {
                light: '#FFFFFF',
                reflection1: '#fa8072', // salmon rgb(250, 128, 114) [250/255, 128/255, 114/255, 1]
                reflection2: '#daa520', // goldenrod rgb(218, 165, 32) [218/255, 165/255, 32/255, 1]
                reflection3: '#2e8b57', // seagreen rgb(46, 139, 87) [46/255, 139/255, 87/255, 1]
                reflection4: '#6a5acd', // slateblue rgb(106, 90, 205) [106/255, 90/255, 205/255, 1]
            };
            gui.addColor(palette, 'light').onChange((val) => {
                const color = new Color(val);
                program1.uniforms.ambientLight.value = color;
                program2.uniforms.ambientLight.value = color;
                program3.uniforms.ambientLight.value = color;
                program4.uniforms.ambientLight.value = color;
            });
            gui.addColor(palette, 'reflection1').onChange((val) => {
                program1.uniforms.materialReflection.value = new Color(val);
            });
            gui.addColor(palette, 'reflection2').onChange((val) => {
                program2.uniforms.materialReflection.value = new Color(val);
            });
            gui.addColor(palette, 'reflection3').onChange((val) => {
                program3.uniforms.materialReflection.value = new Color(val);
            });
            gui.addColor(palette, 'reflection4').onChange((val) => {
                program4.uniforms.materialReflection.value = new Color(val);
            });
        </script>
    </body>
</html>

在这里插入图片描述

如何给物体增加平行光效果?

有向光的漫反射在各个方向上的反射光均匀分布,反射强度与光的射入方向与法线的夹角的余弦成正比。

下面实现给物体增加平行光效果:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>如何给物体增加平行光效果</title>
        <style>
            canvas {
                border: 1px dashed #fa8072;
            }
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script type="module">
            import { Renderer, Camera, Transform, Sphere, Box, Cylinder, Torus, Orbit, Program, Mesh, Color } from './common/lib/ogl/index.mjs';
            // JavaScript Controller Library
            import * as dat from './common/lib/dat.gui.js';
            console.log(dat)

            const canvas = document.querySelector('canvas');
            const renderer = new Renderer({
                canvas,
                width: 512,
                height: 512,
            });

            const gl = renderer.gl;
            gl.clearColor(1, 1, 1, 1);
            const camera = new Camera(gl, {fov: 35});
            camera.position.set(0, 0, 10);
            camera.lookAt([0, 0, 0]);

            const scene = new Transform();

            // 在顶点着色器中计算光线的方向的运算次数少
            const vertex = `
                precision highp float;

                attribute vec3 position;
                attribute vec3 normal;

                uniform mat4 modelViewMatrix;
                uniform mat4 projectionMatrix;
                uniform mat4 viewMatrix;
                uniform mat3 normalMatrix;
                // 添加一道平行光
                uniform vec3 directionalLight;

                varying vec3 vNormal;
                varying vec3 vDir;

                void main() {
                    // 计算光线方向
                    vec4 invDirectional = viewMatrix * vec4(directionalLight, 0.0);
                    vDir = -invDirectional.xyz;

                    // 计算法向量
                    vNormal = normalize(normalMatrix * normal);
                    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                }
            `;

            // 传入环境光 ambientLight 和材质反射率 materialReflection
            // 在片元着色器里,计算光线方向与法向量夹角的余弦,计算出漫反射光。
            const fragment = `
                precision highp float;

                uniform vec3 ambientLight;
                uniform vec3 materialReflection;
                uniform vec3 directionalLightColor;

                varying vec3 vNormal;
                varying vec3 vDir;

                void main() {
                    // 求光线与法线夹角的余弦
                    float cos = max(dot(normalize(vDir), vNormal), 0.0);
                    
                    // 计算漫反射
                    vec3 diffuse = cos * directionalLightColor;
                    
                    // 合成颜色
                    gl_FragColor.rgb = (ambientLight + diffuse) * materialReflection;
                    gl_FragColor.a = 1.0;
                }
            `;

            // 创建四个不同的几何体,初始化它们的环境光 ambientLight 以及材质反射率 materialReflection
            const sphereGeometry = new Sphere(gl);
            const cubeGeometry = new Box(gl);
            const cylinderGeometry = new Cylinder(gl);
            const torusGeometry = new Torus(gl);

            // 添加一个水平向右的白色平行光
            const ambientLight = { value: [1, 1, 1] };

            const directional = {
                directionalLight: {
                    value: [1, 0, 0]
                },
                directionalLightColor: {
                    value: [1, 1, 1]
                }
            };

            const program1 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ambientLight,
                    materialReflection: {value: [250/255, 128/255, 114/255]},
                    ...directional
                },
            });
            const program2 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ambientLight,
                    materialReflection: {value: [218/255, 165/255, 32/255]},
                    ...directional
                },
            });
            const program3 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ambientLight,
                    materialReflection: {value: [46/255, 139/255, 87/255]},
                    ...directional
                },
            });
            const program4 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ambientLight,
                    materialReflection: {value: [106/255, 90/255, 205/255]},
                    ...directional
                },
            });

            const torus = new Mesh(gl, {geometry: torusGeometry, program: program1});
            torus.position.set(0, 1.3, 0);
            torus.setParent(scene);

            const sphere = new Mesh(gl, {geometry: sphereGeometry, program: program2});
            sphere.position.set(1.3, 0, 0);
            sphere.setParent(scene);

            const cube = new Mesh(gl, {geometry: cubeGeometry, program: program3});
            cube.position.set(0, -1.3, 0);
            cube.setParent(scene);

            const cylinder = new Mesh(gl, {geometry: cylinderGeometry, program: program4});
            cylinder.position.set(-1.3, 0, 0);
            cylinder.setParent(scene);

            const controls = new Orbit(camera);

            // 添加动画
            requestAnimationFrame(update);
            function update() {
                requestAnimationFrame(update);
                controls.update();

                torus.rotation.y -= 0.02;
                sphere.rotation.y -= 0.03;
                cube.rotation.y -= 0.04;
                cylinder.rotation.y -= 0.02;

                renderer.render({scene, camera});
            }

            // 添加控制
            const gui = new dat.GUI();
            const palette = {
                light: '#FFFFFF',
                reflection1: '#fa8072', // salmon rgb(250, 128, 114) [250/255, 128/255, 114/255, 1]
                reflection2: '#daa520', // goldenrod rgb(218, 165, 32) [218/255, 165/255, 32/255, 1]
                reflection3: '#2e8b57', // seagreen rgb(46, 139, 87) [46/255, 139/255, 87/255, 1]
                reflection4: '#6a5acd', // slateblue rgb(106, 90, 205) [106/255, 90/255, 205/255, 1]
            };
            gui.addColor(palette, 'light').onChange((val) => {
                const color = new Color(val);
                program1.uniforms.ambientLight.value = color;
                program2.uniforms.ambientLight.value = color;
                program3.uniforms.ambientLight.value = color;
                program4.uniforms.ambientLight.value = color;
            });
            gui.addColor(palette, 'reflection1').onChange((val) => {
                program1.uniforms.materialReflection.value = new Color(val);
            });
            gui.addColor(palette, 'reflection2').onChange((val) => {
                program2.uniforms.materialReflection.value = new Color(val);
            });
            gui.addColor(palette, 'reflection3').onChange((val) => {
                program3.uniforms.materialReflection.value = new Color(val);
            });
            gui.addColor(palette, 'reflection4').onChange((val) => {
                program4.uniforms.materialReflection.value = new Color(val);
            });
        </script>
    </body>
</html>

加了平行光线之后,我们可以感受到明显的阴暗变化,效果如下:

在这里插入图片描述

如何给物体添加点光源?

计算点光源的光照,要先根据光源位置和物体表面相对位置来确定方向,然后再和平行光一样,计算光的方向和物体表面法向的夹角。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>如何给物体添加点光源</title>
        <style>
            canvas {
                border: 1px dashed #fa8072;
            }
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script type="module">
            import { Renderer, Camera, Transform, Sphere, Box, Cylinder, Torus, Orbit, Program, Mesh, Color } from './common/lib/ogl/index.mjs';
            // JavaScript Controller Library
            import * as dat from './common/lib/dat.gui.js';
            console.log(dat)

            const canvas = document.querySelector('canvas');
            const renderer = new Renderer({
                canvas,
                width: 512,
                height: 512,
            });

            const gl = renderer.gl;
            gl.clearColor(1, 1, 1, 1);
            const camera = new Camera(gl, {fov: 35});
            camera.position.set(0, 0, 10);
            camera.lookAt([0, 0, 0]);

            const scene = new Transform();

            // 在顶点着色器中,将物体变换后的坐标传给片元着色器
            const vertex = `
                precision highp float;

                attribute vec3 position;
                attribute vec3 normal;
                uniform mat4 modelViewMatrix;
                uniform mat4 projectionMatrix;
                uniform mat3 normalMatrix;

                varying vec3 vNormal;
                varying vec3 vPos;

                void main() {
                    vec4 pos = modelViewMatrix * vec4(position, 1.0);
                    vPos = pos.xyz;
                    vNormal = normalize(normalMatrix * normal);
                    gl_Position = projectionMatrix * pos;
                }
            `;

            // 传入环境光 ambientLight 和材质反射率 materialReflection
            // 片元着色器中计算光线方向与法向量夹角的余弦
            const fragment = `
                precision highp float;

                uniform vec3 ambientLight;
                uniform vec3 materialReflection;
                uniform vec3 pointLightColor;
                uniform vec3 pointLightPosition;
                uniform mat4 viewMatrix;
                uniform vec3 pointLightDecayFactor;

                varying vec3 vNormal;
                varying vec3 vPos;

                void main() {
                    // 光线到点坐标的方向
                    vec3 dir = (viewMatrix * vec4(pointLightPosition, 1.0)).xyz - vPos;

                    // 与法线夹角余弦
                    float cos = max(dot(normalize(dir), vNormal), 0.0);

                    // 计算漫反射
                    vec3 diffuse = cos * pointLightColor;
                    
                    // 合成颜色
                    gl_FragColor.rgb = (ambientLight + diffuse) * materialReflection;
                    gl_FragColor.a = 1.0;
                }
            `;

            // 创建四个不同的几何体,初始化它们的环境光 ambientLight 以及材质反射率 materialReflection
            const sphereGeometry = new Sphere(gl);
            const cubeGeometry = new Box(gl);
            const cylinderGeometry = new Cylinder(gl);
            const torusGeometry = new Torus(gl);

            // 添加一个水平向右的白色平行光
            const ambientLight = { value: [1, 1, 1] };

            const directional = {
                pointLightPosition: {
                    value: [3, 3, 0]
                },
                pointLightColor: {
                    value: [1, 1, 1]
                }
            };

            const program1 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ambientLight,
                    materialReflection: {value: [250/255, 128/255, 114/255]},
                    ...directional
                },
            });
            const program2 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ambientLight,
                    materialReflection: {value: [218/255, 165/255, 32/255]},
                    ...directional
                },
            });
            const program3 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ambientLight,
                    materialReflection: {value: [46/255, 139/255, 87/255]},
                    ...directional
                },
            });
            const program4 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ambientLight,
                    materialReflection: {value: [106/255, 90/255, 205/255]},
                    ...directional
                },
            });

            const torus = new Mesh(gl, {geometry: torusGeometry, program: program1});
            torus.position.set(0, 1.3, 0);
            torus.setParent(scene);

            const sphere = new Mesh(gl, {geometry: sphereGeometry, program: program2});
            sphere.position.set(1.3, 0, 0);
            sphere.setParent(scene);

            const cube = new Mesh(gl, {geometry: cubeGeometry, program: program3});
            cube.position.set(0, -1.3, 0);
            cube.setParent(scene);

            const cylinder = new Mesh(gl, {geometry: cylinderGeometry, program: program4});
            cylinder.position.set(-1.3, 0, 0);
            cylinder.setParent(scene);

            const controls = new Orbit(camera);

            // 添加动画
            requestAnimationFrame(update);
            function update() {
                requestAnimationFrame(update);
                controls.update();

                torus.rotation.y -= 0.02;
                sphere.rotation.y -= 0.03;
                cube.rotation.y -= 0.04;
                cylinder.rotation.y -= 0.02;

                renderer.render({scene, camera});
            }

            // 添加控制
            const gui = new dat.GUI();
            const palette = {
                light: '#FFFFFF',
                reflection1: '#fa8072', // salmon rgb(250, 128, 114) [250/255, 128/255, 114/255, 1]
                reflection2: '#daa520', // goldenrod rgb(218, 165, 32) [218/255, 165/255, 32/255, 1]
                reflection3: '#2e8b57', // seagreen rgb(46, 139, 87) [46/255, 139/255, 87/255, 1]
                reflection4: '#6a5acd', // slateblue rgb(106, 90, 205) [106/255, 90/255, 205/255, 1]
            };
            gui.addColor(palette, 'light').onChange((val) => {
                const color = new Color(val);
                program1.uniforms.ambientLight.value = color;
                program2.uniforms.ambientLight.value = color;
                program3.uniforms.ambientLight.value = color;
                program4.uniforms.ambientLight.value = color;
            });
            gui.addColor(palette, 'reflection1').onChange((val) => {
                program1.uniforms.materialReflection.value = new Color(val);
            });
            gui.addColor(palette, 'reflection2').onChange((val) => {
                program2.uniforms.materialReflection.value = new Color(val);
            });
            gui.addColor(palette, 'reflection3').onChange((val) => {
                program3.uniforms.materialReflection.value = new Color(val);
            });
            gui.addColor(palette, 'reflection4').onChange((val) => {
                program4.uniforms.materialReflection.value = new Color(val);
            });
        </script>
    </body>
</html>

在这里插入图片描述

点光源的衰减

真实世界中,点光源的光照强度会随着空间的距离增加而衰减。我们需要模拟一个衰减系数出来。

衰减系数等于一个常量 d 0 d_0 d0​(通常为 1),除以衰减函数 P。衰减函数可以用一个二次多项式 P 来描述,公式如下:

  • A、B、C 为常量
  • z 是当前位置到点光源的距离

在这里插入图片描述

然后利用光线到点坐标的距离,用来计算衰减,实现如下:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>点光源的衰减</title>
        <style>
            canvas {
                border: 1px dashed #fa8072;
            }
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script type="module">
            import { Renderer, Camera, Transform, Sphere, Box, Cylinder, Torus, Orbit, Program, Mesh, Color } from './common/lib/ogl/index.mjs';
            // JavaScript Controller Library
            import * as dat from './common/lib/dat.gui.js';
            console.log(dat)

            const canvas = document.querySelector('canvas');
            const renderer = new Renderer({
                canvas,
                width: 512,
                height: 512,
            });

            const gl = renderer.gl;
            gl.clearColor(1, 1, 1, 1);
            const camera = new Camera(gl, {fov: 35});
            camera.position.set(0, 0, 10);
            camera.lookAt([0, 0, 0]);

            const scene = new Transform();

            // 在顶点着色器中,将物体变换后的坐标传给片元着色器
            const vertex = `
                precision highp float;

                attribute vec3 position;
                attribute vec3 normal;
                uniform mat4 modelViewMatrix;
                uniform mat4 projectionMatrix;
                uniform mat3 normalMatrix;

                varying vec3 vNormal;
                varying vec3 vPos;

                void main() {
                    vec4 pos = modelViewMatrix * vec4(position, 1.0);
                    vPos = pos.xyz;
                    vNormal = normalize(normalMatrix * normal);
                    gl_Position = projectionMatrix * pos;
                }
            `;

            // 传入环境光 ambientLight 和材质反射率 materialReflection
            // 片元着色器中计算光线方向与法向量夹角的余弦
            const fragment = `
                precision highp float;

                uniform vec3 ambientLight;
                uniform vec3 materialReflection;
                uniform vec3 pointLightColor;
                uniform vec3 pointLightPosition;
                uniform mat4 viewMatrix;
                uniform vec3 pointLightDecayFactor;

                varying vec3 vNormal;
                varying vec3 vPos;

                void main() {
                    // 光线到点坐标的方向
                    vec3 dir = (viewMatrix * vec4(pointLightPosition, 1.0)).xyz - vPos;
                    
                    // 光线到点坐标的距离,用来计算衰减
                    float dis = length(dir);

                    // 与法线夹角余弦
                    float cos = max(dot(normalize(dir), vNormal), 0.0);

                    // 计算衰减
                    float decay = min(1.0, 1.0 /
                        (pointLightDecayFactor.x * pow(dis, 2.0) + pointLightDecayFactor.y * dis + pointLightDecayFactor.z));
                    
                    // 计算漫反射
                    vec3 diffuse = decay * cos * pointLightColor;
                    
                    // 合成颜色
                    gl_FragColor.rgb = (ambientLight + diffuse) * materialReflection;
                    gl_FragColor.a = 1.0;
                }
            `;

            // 创建四个不同的几何体,初始化它们的环境光 ambientLight 以及材质反射率 materialReflection
            const sphereGeometry = new Sphere(gl);
            const cubeGeometry = new Box(gl);
            const cylinderGeometry = new Cylinder(gl);
            const torusGeometry = new Torus(gl);

            // 添加一个水平向右的白色平行光
            const ambientLight = { value: [1, 1, 1] };

            const directional = {
                pointLightPosition: {
                    value: [3, 3, 0]
                },
                pointLightColor: {
                    value: [1, 1, 1]
                },
                pointLightDecayFactor: { value: [0.08, 0, 1] },
            };

            const program1 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ambientLight,
                    materialReflection: {value: [250/255, 128/255, 114/255]},
                    ...directional
                },
            });
            const program2 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ambientLight,
                    materialReflection: {value: [218/255, 165/255, 32/255]},
                    ...directional
                },
            });
            const program3 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ambientLight,
                    materialReflection: {value: [46/255, 139/255, 87/255]},
                    ...directional
                },
            });
            const program4 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ambientLight,
                    materialReflection: {value: [106/255, 90/255, 205/255]},
                    ...directional
                },
            });

            const torus = new Mesh(gl, {geometry: torusGeometry, program: program1});
            torus.position.set(0, 1.3, 0);
            torus.setParent(scene);

            const sphere = new Mesh(gl, {geometry: sphereGeometry, program: program2});
            sphere.position.set(1.3, 0, 0);
            sphere.setParent(scene);

            const cube = new Mesh(gl, {geometry: cubeGeometry, program: program3});
            cube.position.set(0, -1.3, 0);
            cube.setParent(scene);

            const cylinder = new Mesh(gl, {geometry: cylinderGeometry, program: program4});
            cylinder.position.set(-1.3, 0, 0);
            cylinder.setParent(scene);

            const controls = new Orbit(camera);

            // 添加动画
            requestAnimationFrame(update);
            function update() {
                requestAnimationFrame(update);
                controls.update();

                torus.rotation.y -= 0.02;
                sphere.rotation.y -= 0.03;
                cube.rotation.y -= 0.04;
                cylinder.rotation.y -= 0.02;

                renderer.render({scene, camera});
            }

            // 添加控制
            const gui = new dat.GUI();
            const palette = {
                light: '#FFFFFF',
                reflection1: '#fa8072', // salmon rgb(250, 128, 114) [250/255, 128/255, 114/255, 1]
                reflection2: '#daa520', // goldenrod rgb(218, 165, 32) [218/255, 165/255, 32/255, 1]
                reflection3: '#2e8b57', // seagreen rgb(46, 139, 87) [46/255, 139/255, 87/255, 1]
                reflection4: '#6a5acd', // slateblue rgb(106, 90, 205) [106/255, 90/255, 205/255, 1]
            };
            gui.addColor(palette, 'light').onChange((val) => {
                const color = new Color(val);
                program1.uniforms.ambientLight.value = color;
                program2.uniforms.ambientLight.value = color;
                program3.uniforms.ambientLight.value = color;
                program4.uniforms.ambientLight.value = color;
            });
            gui.addColor(palette, 'reflection1').onChange((val) => {
                program1.uniforms.materialReflection.value = new Color(val);
            });
            gui.addColor(palette, 'reflection2').onChange((val) => {
                program2.uniforms.materialReflection.value = new Color(val);
            });
            gui.addColor(palette, 'reflection3').onChange((val) => {
                program3.uniforms.materialReflection.value = new Color(val);
            });
            gui.addColor(palette, 'reflection4').onChange((val) => {
                program4.uniforms.materialReflection.value = new Color(val);
            });
        </script>
    </body>
</html>

衰减对比效果如下:光线强度随着距离衰减,可以右边较远的几何体几乎没有光照。

在这里插入图片描述

如何给物体添加聚光灯效果?

与点光源相比,聚光灯相对来说比较复杂,要用 5 个参数来描述:

  1. spotLightColor 聚光灯颜色
  2. spotLightPosition 聚光灯位置
  3. spotLightDecayFactor 聚光灯衰减系数
  4. spotLightDirection 聚光灯方向
  5. spotLightAngle 聚光灯角度

利用聚光灯方向和角度,就可以求法向量与光线方向夹角的余弦值,这个值可以判断坐标是否在夹角内,最终的光照效果就只会出现在光照的角度内。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>如何给物体添加聚光灯效果</title>
        <style>
            canvas {
                border: 1px dashed #fa8072;
            }
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script type="module">
            import { Renderer, Camera, Transform, Sphere, Box, Cylinder, Torus, Orbit, Program, Mesh, Color } from './common/lib/ogl/index.mjs';
            // JavaScript Controller Library
            import * as dat from './common/lib/dat.gui.js';
            console.log(dat)

            const canvas = document.querySelector('canvas');
            const renderer = new Renderer({
                canvas,
                width: 512,
                height: 512,
            });

            const gl = renderer.gl;
            gl.clearColor(1, 1, 1, 1);
            const camera = new Camera(gl, {fov: 35});
            camera.position.set(0, 0, 10);
            camera.lookAt([0, 0, 0]);

            const scene = new Transform();

            // 在顶点着色器中,将物体变换后的坐标传给片元着色器
            const vertex = `
                precision highp float;

                attribute vec3 position;
                attribute vec3 normal;
                uniform mat4 modelViewMatrix;
                uniform mat4 projectionMatrix;
                uniform mat3 normalMatrix;

                varying vec3 vNormal;
                varying vec3 vPos;

                void main() {
                    vec4 pos = modelViewMatrix * vec4(position, 1.0);
                    vPos = pos.xyz;
                    vNormal = normalize(normalMatrix * normal);
                    gl_Position = projectionMatrix * pos;
                }
            `;

            // 传入环境光 ambientLight 和材质反射率 materialReflection
            // 片元着色器中求法向量与光线方向夹角的余弦值
            const fragment = `
                precision highp float;

                uniform mat4 viewMatrix;
                uniform vec3 ambientLight;
                uniform vec3 materialReflection;
                uniform vec3 spotLightColor;
                uniform vec3 spotLightPosition;
                uniform vec3 spotLightDecayFactor;
                uniform vec3 spotLightDirection;
                uniform float spotLightAngle;

                varying vec3 vNormal;
                varying vec3 vPos;

                void main() {
                    // 光线到点坐标的方向
                    vec3 invLight = (viewMatrix * vec4(spotLightPosition, 1.0)).xyz - vPos;
                    vec3 invNormal = normalize(invLight);

                    // 光线到点坐标的距离,用来计算衰减
                    float dis = length(invLight);
                    
                    // 聚光灯的朝向
                    vec3 dir = (viewMatrix * vec4(spotLightDirection, 0.0)).xyz;

                    // 通过余弦值判断夹角范围
                    float ang = cos(spotLightAngle);
                    float r = step(ang, dot(invNormal, normalize(-dir)));

                    // 与法线夹角余弦
                    float cos = max(dot(invNormal, vNormal), 0.0);
                    // 计算衰减
                    float decay = min(1.0, 1.0 /
                        (spotLightDecayFactor.x * pow(dis, 2.0) + spotLightDecayFactor.y * dis + spotLightDecayFactor.z));
                    
                    // 计算漫反射
                    vec3 diffuse = r * decay * cos * spotLightColor;
                    
                    // 合成颜色
                    gl_FragColor.rgb = (ambientLight + diffuse) * materialReflection;
                    gl_FragColor.a = 1.0;
                }
            `;

            // 创建四个不同的几何体,初始化它们的环境光 ambientLight 以及材质反射率 materialReflection
            const sphereGeometry = new Sphere(gl);
            const cubeGeometry = new Box(gl);
            const cylinderGeometry = new Cylinder(gl);
            const torusGeometry = new Torus(gl);

            // 添加一个水平向右的白色平行光
            const ambientLight = { value: [1, 1, 1] };

            const directional = {
                spotLightPosition: { value: [3, 3, 0] },
                spotLightColor: { value: [1, 1, 1] },
                spotLightDecayFactor: { value: [0.05, 0, 1] },
                spotLightDirection: { value: [-1, -1, 0] },
                spotLightAngle: { value: Math.PI / 12 },
            };

            const program1 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ambientLight,
                    materialReflection: {value: [250/255, 128/255, 114/255]},
                    ...directional
                },
            });
            const program2 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ambientLight,
                    materialReflection: {value: [218/255, 165/255, 32/255]},
                    ...directional
                },
            });
            const program3 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ambientLight,
                    materialReflection: {value: [46/255, 139/255, 87/255]},
                    ...directional
                },
            });
            const program4 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ambientLight,
                    materialReflection: {value: [106/255, 90/255, 205/255]},
                    ...directional
                },
            });

            const torus = new Mesh(gl, {geometry: torusGeometry, program: program1});
            torus.position.set(0, 1.3, 0);
            torus.setParent(scene);

            const sphere = new Mesh(gl, {geometry: sphereGeometry, program: program2});
            sphere.position.set(1.3, 0, 0);
            sphere.setParent(scene);

            const cube = new Mesh(gl, {geometry: cubeGeometry, program: program3});
            cube.position.set(0, -1.3, 0);
            cube.setParent(scene);

            const cylinder = new Mesh(gl, {geometry: cylinderGeometry, program: program4});
            cylinder.position.set(-1.3, 0, 0);
            cylinder.setParent(scene);

            const controls = new Orbit(camera);

            // 添加动画
            requestAnimationFrame(update);
            function update() {
                requestAnimationFrame(update);
                controls.update();

                torus.rotation.y -= 0.02;
                sphere.rotation.y -= 0.03;
                cube.rotation.y -= 0.04;
                cylinder.rotation.y -= 0.02;

                renderer.render({scene, camera});
            }

            // 添加控制
            const gui = new dat.GUI();
            const palette = {
                light: '#FFFFFF',
                reflection1: '#fa8072', // salmon rgb(250, 128, 114) [250/255, 128/255, 114/255, 1]
                reflection2: '#daa520', // goldenrod rgb(218, 165, 32) [218/255, 165/255, 32/255, 1]
                reflection3: '#2e8b57', // seagreen rgb(46, 139, 87) [46/255, 139/255, 87/255, 1]
                reflection4: '#6a5acd', // slateblue rgb(106, 90, 205) [106/255, 90/255, 205/255, 1]
            };
            gui.addColor(palette, 'light').onChange((val) => {
                const color = new Color(val);
                program1.uniforms.ambientLight.value = color;
                program2.uniforms.ambientLight.value = color;
                program3.uniforms.ambientLight.value = color;
                program4.uniforms.ambientLight.value = color;
            });
            gui.addColor(palette, 'reflection1').onChange((val) => {
                program1.uniforms.materialReflection.value = new Color(val);
            });
            gui.addColor(palette, 'reflection2').onChange((val) => {
                program2.uniforms.materialReflection.value = new Color(val);
            });
            gui.addColor(palette, 'reflection3').onChange((val) => {
                program3.uniforms.materialReflection.value = new Color(val);
            });
            gui.addColor(palette, 'reflection4').onChange((val) => {
                program4.uniforms.materialReflection.value = new Color(val);
            });
        </script>
    </body>
</html>

大致的效果如下:可以明显的感受到聚光灯的效果

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/68392.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

事务相关知识集锦

作者&#xff1a;李玉亮 引言 数据库事务与大多数后端软件开发人员的工作密不可分&#xff0c;本文从事务理论、事务技术、事务实践等方面对常用的相关事务知识进行整理总结&#xff0c;供大家参考。 &#xfeff;&#xfeff; 事务理论介绍 事务定义 在数据库管理系统中&…

Navicat!OceanBase社区版新朋友来啦!

引言&#xff1a; 近期&#xff0c;Navicat Premium 16.1与Navicat for MySQL 16.1版本&#xff0c;将新增 OceanBase 社区版兼容功能&#xff0c;为用户在使用 OceanBase 社区版迁移过程中提供数据库管理开发工具新的选择&#xff0c;旨在帮助用户提升工作效率&#xff0c;减少…

「模型即服务AI」1分钟调用SOTA人脸检测,同时搭建时光相册小应用

时光相册应用效果一、物料 人脸检测&#xff1a;https://modelscope.cn/models/damo/cv_resnet101_face-detection_cvpr22papermogface/summary 时光相册&#xff1a; https://modelscope.cn/studios/damo/face_album/summary 二、背景 最近有两个计算机应用发展的方向正在潜…

社区疫情防控系统毕业设计,社情疫情防控系统设计与实现,毕业设计怎么写论文源码开题报告需求分析怎么做

项目背景和意义 目的&#xff1a;本课题主要目标是设计并能够实现一个基于web网页的疫情下社区健康评估系统&#xff0c;整个网站项目使用了B/S架构&#xff0c;基于java的springboot框架下开发&#xff1b;通过后台设置网站信息&#xff0c;设置广告信息&#xff0c;查看和管理…

2022选择了交大,回顾这一年的成长

Datawhale干货 作者&#xff1a;王琦&#xff0c;上海交通大学&#xff0c;Datawhale成员2022年是颇为忙碌的一年&#xff0c;今年我从中国科学院大学毕业、申请上了上海交通大学的博士、参与贡献了开源教程“Easy-RL”&#xff08;5.6K GitHub Stars&#xff09;、出版了著作《…

【面试高频题】难度 2/5,回溯算法经典运用

题目描述 这是 LeetCode 上的 93. 复原 IP 地址 &#xff0c;难度为 中等。 Tag : 「回溯」、「DFS」 有效 IP 地址 正好由四个整数&#xff08;每个整数位于 0 到 255 之间组成&#xff0c;且不能含有前导 0&#xff09;&#xff0c;整数之间用 . 分隔。 例如&#xff1a;&…

按指定频次对时间序列数据进行分组pd.grouper()方法

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 构造时间序列为索引的DataFrame 按照指定的时间间隔分组统计 df.grouper() 选择题 关于以下python代码说法错误的一项是? import pandas as pd ipd.to_datetime(["2022-12-01",…

面试官:MySQL 数据库查询慢,除了索引问题还可能是什么原因?面试架构师必备知识

文章目录数据库查询流程慢查询分析索引相关原因连接数过小buffer pool太小还有哪些骚操作&#xff1f;总结mysql查询为什么会慢&#xff0c;关于这个问题&#xff0c;在实际开发经常会遇到&#xff0c;而面试中&#xff0c;也是个高频题。遇到这种问题&#xff0c;我们一般也会…

Ubuntu20运行SegNeXt代码提取道路水体(一)——从零开始运行代码过程摸索

SegNeXt代码最近可谓是非常火 应导师的要求打工人需要学习一下新的代码 但是网上千篇一律都是论文的讲解 如何跑通代码并且使用自己的数据跑出一个好的结果却没有一个详细的教程 那么就让我自己来从零开始跑通代码吧 下载代码和数据 首先咱们先别想着用自己的数据 从githu…

iTOP3568开发板ubuntu系统修改开机联网等待时间

启动开发板使用以下命令对 networking.service 文件进行修改&#xff0c;如下图所示&#xff1a; vi /etc/systemd/system/network-online.target.wants/networking.service 修改完后保存退出&#xff0c;重启开发板就会看到等待时间变为 2min 了&#xff0c;如下图所示&…

软件安全测试-网络相关基础知识

目录 1. OSI 网络模型 2. TCP/IP协议 2.1 TCP 协议分层 2.2 TCP 协议内容 2.3 应用层活动 2.4 传输层活动 2.4.1 建立连接三次握手 2.4.2 断开连接四次握手 2.4.3. 数据打包与分解 2.5 网络层活动 2.5.1 IP寻址 2.5.2 ARP协议获取MAC地址 2.5.3 BGP外部网关协议…

Neuroscout:可推广和重复利用的fMRI研究统一平台

摘要 功能磁共振成像 (fMRI) 已经彻底改变了认知神经科学&#xff0c;但方法上的障碍限制了研究 结果的普遍性。Neuroscout&#xff0c;一个端到端分析自然功能磁共振成像数据 的平台&#xff0c; 旨在促进稳健和普遍化的研究推广。Neuroscout利用最先进的机器学习模型来自动注…

一文解决IDEA中文乱码问题

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;JAVA开发者…

一张图让你牢记MySQL主从复制原理|原创

本文深入浅出的讲解了MySQL面试中的必考内容——主从同步原理&#xff0c;牢记文中的主从同步流程图即可&#xff01;点击上方“后端开发技术”&#xff0c;选择“设为星标” &#xff0c;优质资源及时送达为什么需要主从复制&#xff1f;1、读写分离&#xff0c;增强MySQL数据…

前端面试题合集

UDP和TCP有什么区别 TCP协议在传送数据段的时候要给段标号&#xff1b;UDP协议不TCP协议可靠&#xff1b;UDP协议不可靠TCP协议是面向连接&#xff1b;UDP协议采用无连接TCP协议负载较高&#xff0c;采用虚电路&#xff1b;UDP采用无连接TCP协议的发送方要确认接收方是否收到数…

[附源码]Python计算机毕业设计Django心理健康系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

【Rasa+Pycharm+Tensorflow】控制台实现智能客服问答实战(附源码和数据集 超详细)

需要源码和数据集请点赞关注收藏后评论区留言~~~ 一、数据准备 1&#xff1a;下面以pychar为环境介绍操作步骤&#xff0c;选择file-new project 创建一个新项目 new environment using 选择 Virtualenv 命名虚拟环境的名称&#xff0c;这里假定虚拟环境的名称为venv&#xff…

uniapp获得某个元素的高度并移动到该高度【伸手党福利】

uniapp获得某个元素的高度并移动到该高度 <view class"scrolls"> ... </view>//等view加载完了才取高度setTimeout(()>{const query uni.createSelectorQuery().in(this);query.select(.scrolls).boundingClientRect(data > {console.log("…

20221207英语学习

今日新词&#xff1a; work v.劳动, 干活; 工作; 起作用, 奏效; 运行 mentality n.〈常贬〉心态&#xff0c;心性&#xff1b;思想方法 copyright n.版权, 著作权 turkey n.火鸡&#xff1b;火鸡肉&#xff1b;&#xff08;Turkey&#xff09;土耳其 best-selling adj.最畅…

知识图谱-生物信息学-医学顶刊论文(Briefings in Bioinformatics-2022):基于异构图GCN和GAT的DTI预测

(2022.4.16)Briefings-DTI-HETA&#xff1a;基于异构图GCN和GAT的DTI预测 目录 (2022.4.16)Briefings-DTI-HETA&#xff1a;基于异构图GCN和GAT的DTI预测 摘要1.引言2.模型方法 2.1 定义3.1 异构图上的GCN3.2 图注意机制3.3 链接预测 4.实验 4.1 案例分析 论文题目&#xff1…