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

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

说明

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



光照效果简介


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


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


   环境光(Ambient Light):指物体所在的三维空间中天然的光,它充满整个空间,在每一处的光照强度都一样。


       特点1:在空间中均匀分布,在任何位置上环境光的颜色都相同


       特点2:环境光没有方向,与物体的材质有关


   平行光(Directional Light):平行光是朝着某个方向照射的光,能够照亮几何体的一部分表面,它属于有向光。


   点光源(Positional Light):指空间中某一点发出的光,与方向光不同的是,点光源不仅有方向属性,还有位置属性。


   聚光灯(Spot Light):与点光源相比,聚光灯增加了方向以及角度范围,只有在这个范围内,光线才能照到。


点光源跟平行光的示意图:

a7cc914b327648169fa6de01c5ba207a.png

聚光灯示意图:64705089a83a4af5aae67592c21fdc6c.png


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


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


漫反射示意图:


3e18d5b4c0734cfdaf855e5e49709f75.png


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


4633d0993f7849e991e2a966ff1adcb2.png



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


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

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


90efab4a41c643d4a7257a4a77305ce1.png


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

<!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>


image.gif


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

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

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

<!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>


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

image.gif


如何给物体添加点光源?

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

<!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>

image.gif



点光源的衰减

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

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


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


e3fe9d52f27c4f8dbb1bfd816079ffd4.png


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

<!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>


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

3198c53da2b5428a99ac5b691324ef4e.png


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

与点光源相比,聚光灯相对来说比较复杂,要用 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>


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

661f234dfe3c4c50bb9dd3108c28825f.gif






目录
相关文章
|
机器学习/深度学习 数据可视化 图形学
【视觉高级篇】24 # 如何模拟光照让3D场景更逼真?(下)
【视觉高级篇】24 # 如何模拟光照让3D场景更逼真?(下)
86 0
【视觉高级篇】24 # 如何模拟光照让3D场景更逼真?(下)
|
8天前
|
存储 编解码 安全
带三维重建和还原的PACS源码 医学影像PACS系统源码
带三维重建和还原的PACS源码 医学影像PACS系统源码 PACS及影像存取与传输系统”( Picture Archiving and Communication System),为以实现医学影像数字化存储、诊断为核心任务,从医学影像设备(如CT、CR、DR、MR、DSA、RF等)获取影像,集中存储、综合管理医学影像及病人相关信息,建立数字化工作流程。系统可实现检查预约、病人信息登记、计算机阅片、电子报告书写、胶片打印、数据备份等一系列满足影像科室日常工作的功能,并且由于影像数字化存储,用户可利用影像处理与测量技术辅助诊断、方便快捷地查找资料或利用网络将资料传输至临床科室,还可与医院HIS、L
22 0
|
8天前
|
存储 数据采集 固态存储
带三维重建和还原功能的医学影像管理系统(pacs)源码
带三维重建和还原功能的医学影像管理系统(pacs)源码
59 0
|
8天前
|
存储 数据可视化 vr&ar
突破传统 重新定义:3D医学影像PACS系统源码(包含RIS放射信息) 实现三维重建与还原
突破传统,重新定义PACS/RIS服务,洞察用户需求,关注应用场景,新一代PACS/RIS系统,系统顶层设计采用集中+分布式架构,满足医院影像全流程业务运行,同时各模块均可独立部署,满足医院未来影像信息化扩展新需求、感受新时代影像服务便捷性、易用性!系统基于平台化设计,与第三方服务自然接入无压力,从功能多样化到调阅速度快;覆盖(放射、超声、内镜、病理、核医学、心血管、临床科室等,是以影像采集、传输、存储、诊断、报告书写和科室管理)为核心应用的模块化PACS/RIS系统,实现了全院级影像信息的合理共享与应用。
23 0
突破传统 重新定义:3D医学影像PACS系统源码(包含RIS放射信息) 实现三维重建与还原
|
8天前
|
存储 数据采集 编解码
【PACS】医学影像管理系统源码带三维重建后处理技术
【PACS】医学影像管理系统源码带三维重建后处理技术
62 0
|
8天前
|
C++
【C++医学影像PACS】CT检查中的三维重建是什么检查?
【C++医学影像PACS】CT检查中的三维重建是什么检查?
71 0
|
8天前
|
数据采集 存储 数据可视化
医院影像PACS系统三维重建技术(获取数据、预处理、重建)
开放式体系结构,完全符合DICOM3.0标准,提供HL7标准接口,可实现与提供相应标准接口的HIS系统以及其他医学信息系统间的数据通信。
38 3
|
8天前
|
存储 编解码 监控
【C++】医学影像PACS三维重建后处理系统源码
系统完全符合国际标准的DICOM3.0标准
34 2
|
8天前
|
存储
医院PACS系统全套源码 强大的三维重建功能
对非DICOM影像,如超声、病理、心电图等进行了集成,做到了可以同时处理DICOM标准图像和非DICOM图像。
24 1
|
10月前
|
存储 数据库 数据安全/隐私保护
基于C++开发,支持三维重建,多平面重建技术的医学影像PACS系统源码
支持非DICOM标准的影像设备的图像采集和处理。 3)支持各种扫描仪、数码相机等影像输入设备。 4)支持各大主流厂商的CT、MR、DSA、ECT、US、数字胃肠、内镜等影像设备; 5)支持所有的DICOM相机,支持各大厂家的激光相机。 6)系统完全支持HL7接口和ICD—10编码,可与HIS系统无缝连接。 7)提供全院级、科室级工作站以及远程会诊工作站,三维重建,多平面重建。
基于C++开发,支持三维重建,多平面重建技术的医学影像PACS系统源码