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

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

说明

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




什么是镜面反射?

如果若干平行光照射在表面光滑的物体上,反射出来的光依然平行,这种反射就是镜面反射。越光滑的材质,它的镜面反射效果也就越强,并且物体表面会有闪耀的光斑,也叫镜面高光

15968d062e2b4b1daa747368f973752c.png

镜面反射的性质:入射光与法线的夹角等于反射光与法线的夹角




如何实现有向光的镜面反射?


实现镜面反射效果的步骤:


  1. 求出反射光线的方向向量
  2. 根据相机位置计算视线与反射光线夹角的余弦
  3. 使用系数和指数函数设置镜面反射强度
  4. 将漫反射和镜面反射结合起来,让距离光源近的物体上形成光斑

下面以点光源为例来实现光斑:

<!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 viewMatrix;
                uniform mat4 projectionMatrix;
                uniform mat3 normalMatrix;
                uniform vec3 cameraPosition;
                varying vec3 vNormal;
                varying vec3 vPos;
                varying vec3 vCameraPos;
                void main() {
                    vec4 pos = modelViewMatrix * vec4(position, 1.0);
                    vPos = pos.xyz;
                    // 求光源与点坐标的方向向量
                    vCameraPos = (viewMatrix * vec4(cameraPosition, 1.0)).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;
                varying vec3 vCameraPos;
                void main() {
                    // 光线到点坐标的方向
                    vec3 dir = (viewMatrix * vec4(pointLightPosition, 1.0)).xyz - vPos;
                    // 光线到点坐标的距离,用来计算衰减
                    float dis = length(dir);
                    // 归一化
                    dir = normalize(dir);
                    // 与法线夹角余弦
                    float cos = max(dot(normalize(dir), vNormal), 0.0);
                    // 反射光线:使用 GLSL 的内置函数 reflect,这个函数能够返回一个向量相对于某个法向量的反射向量
                    vec3 reflectionLight = reflect(-dir, vNormal);
                    vec3 eyeDirection = vCameraPos - vPos;
                    eyeDirection = normalize(eyeDirection);
                    // 与视线夹角余弦
                    float eyeCos = max(dot(eyeDirection, reflectionLight), 0.0);
                    // 镜面反射:指数取 20.0,系数取 3.0。
                    // 指数越大,镜面越聚焦,高光的光斑范围就越小。
                    // 系数能改变反射亮度,系数越大,反射的亮度就越高。
                    float specular = 3.0 *  pow(eyeCos, 20.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 = specular + (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: [0.5, 0.5, 0.5] },
                pointLightDecayFactor: { value: [0, 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>


ffb93a806aec4ebaacc6c41986e7c715.gif



什么是 Phong 反射模型?


冯氏反射模型是由美国犹他大学(University of Utah)的 Bui Tuong Phong 于1975年在他的博士论文中提出的,都用其名字命名。


Phong 光照模型是真实图形学中提出的第一个有影响的光照明模型,该模型只考虑物体对直接光照的反射作用,认为环境光是常量,没有考虑物体之间相互的反射光,物体间的反射光只用环境光表示。


Phong光照模型属于简单光照模型。


Phong 模型认为物体表面反射光线由三部分组成:

  • 环境光(Ambient):场景中的其他间接光照
  • 漫反射(Diffuse):散射部分(大但不光亮)
  • 高光反射(Specular):镜面反射部分(小而亮)


5f2196bd33f24cc78093c74db69e6ca4.png


在上图中,光线是白色的,环境光和漫反射部分是蓝色的,高光部分是白色的。


高光部分反射的光区域比较小,但强度很大;漫反射部分的强度根据物体表面方向的不同而不同;而环境光部分是跟方向无关的。


Phong 反射模型的完整公式如下:


c6c6d8e8e4d54a5099ca89a6fc4052d5.png



光源部分:

   lights:所有光源的集合,对于每盏光,可分为高光和漫反射两部分

   i s i_s is:光源高光部分的强度(可以理解为就是RGB)

   i d i_d id:光源漫反射部分的强度(可以理解为就是RGB)

   i a i_a ia:环境光部分的强度(可以理解为就是RGB)



场景中材质的参数:

   k s k_s ks:对入射光的高光反射常数(镜面反射系数)

   k d k_d kd:对入射光的漫反射常数

   k a k_a ka:对环境光的反射常数

   α:是和物体材质有关的常量,决定了镜面高光的范围。光泽度 α 越大,则亮点越小。



几个向量(全部归一化):

   L m ^ \hat{L_m} Lm^:物体表面某点指向光源m的位置的向量

   N ^ \hat{N} N^:物体表面某点的法线

   R m ^ \hat{R_m} Rm^:光源在物体表面某点发生镜面反射的方向

   V ^ \hat{V} V^:物体表面某点指向摄像机位置的向量



如何实现完整的 Phong 反射模型?

Phong 反射模型的实现整个过程分为三步:定义光源模型、定义几何体材质和实现着色器




1、定义光源模型image.png

实现定义一个 Phong 类:用于添加和删除光源,并把光源的属性通过 uniforms 访问器属性转换成对应的 uniform 变量。


class Phong {
  constructor(ambientLight = [0.5, 0.5, 0.5]) {
    this.ambientLight = ambientLight;
    this.directionalLights = new Set();
    this.pointLights = new Set();
    this.spotLights = new Set();
  }
  addLight(light) {
    const {position, direction, color, decay, angle} = light;
    if(!position && !direction) throw new TypeError('invalid light');
    light.color = color || [1, 1, 1];
    if(!position) this.directionalLights.add(light);
    else {
      light.decay = decay || [0, 0, 1];
      if(!angle) {
        this.pointLights.add(light);
      } else {
        this.spotLights.add(light);
      }
    }
  }
  removeLight(light) {
    if(this.directionalLights.has(light)) this.directionalLights.delete(light);
    else if(this.pointLights.has(light)) this.pointLights.delete(light);
    else if(this.spotLights.has(light)) this.spotLights.delete(light);
  }
  get uniforms() {
    const MAX_LIGHT_COUNT = 16; // 最多每种光源设置16个
    this._lightData = this._lightData || {};
    const lightData = this._lightData;
    lightData.directionalLightDirection = lightData.directionalLightDirection || {value: new Float32Array(MAX_LIGHT_COUNT * 3)};
    lightData.directionalLightColor = lightData.directionalLightColor || {value: new Float32Array(MAX_LIGHT_COUNT * 3)};
    lightData.pointLightPosition = lightData.pointLightPosition || {value: new Float32Array(MAX_LIGHT_COUNT * 3)};
    lightData.pointLightColor = lightData.pointLightColor || {value: new Float32Array(MAX_LIGHT_COUNT * 3)};
    lightData.pointLightDecay = lightData.pointLightDecay || {value: new Float32Array(MAX_LIGHT_COUNT * 3)};
    lightData.spotLightDirection = lightData.spotLightDirection || {value: new Float32Array(MAX_LIGHT_COUNT * 3)};
    lightData.spotLightPosition = lightData.spotLightPosition || {value: new Float32Array(MAX_LIGHT_COUNT * 3)};
    lightData.spotLightColor = lightData.spotLightColor || {value: new Float32Array(MAX_LIGHT_COUNT * 3)};
    lightData.spotLightDecay = lightData.spotLightDecay || {value: new Float32Array(MAX_LIGHT_COUNT * 3)};
    lightData.spotLightAngle = lightData.spotLightAngle || {value: new Float32Array(MAX_LIGHT_COUNT)};
    [...this.directionalLights].forEach((light, idx) => {
      lightData.directionalLightDirection.value.set(light.direction, idx * 3);
      lightData.directionalLightColor.value.set(light.color, idx * 3);
    });
    [...this.pointLights].forEach((light, idx) => {
      lightData.pointLightPosition.value.set(light.position, idx * 3);
      lightData.pointLightColor.value.set(light.color, idx * 3);
      lightData.pointLightDecay.value.set(light.decay, idx * 3);
    });
    [...this.spotLights].forEach((light, idx) => {
      lightData.spotLightPosition.value.set(light.position, idx * 3);
      lightData.spotLightColor.value.set(light.color, idx * 3);
      lightData.spotLightDecay.value.set(light.decay, idx * 3);
      lightData.spotLightDirection.value.set(light.direction, idx * 3);
      lightData.spotLightAngle.value[idx] = light.angle;
    });
    return {
      ambientLight: {value: this.ambientLight},
      ...lightData,
    };
  }
}



2、定义几何体材质

实现一个 Matrial 类,来定义物体的材质。通过 uniforms 访问器属性,获得它的 uniform 数据结构形式。

class Material {
  constructor(reflection, specularFactor = 0, shininess = 50) {
    this.reflection = reflection;
    this.specularFactor = specularFactor;
    this.shininess = shininess;
  }
  get uniforms() {
    return {
      materialReflection: {value: this.reflection},
      specularFactor: {value: this.specularFactor},
      shininess: {value: this.shininess},
    };
  }
}

3、实现着色器

下面看一下支持 phong 反射模型的片元着色器是怎么处理的。

首先声明了 vec3 和 float 数组,数组的大小为 16。

#define MAX_LIGHT_COUNT 16
uniform mat4 viewMatrix;
uniform vec3 ambientLight;
uniform vec3 directionalLightDirection[MAX_LIGHT_COUNT];
uniform vec3 directionalLightColor[MAX_LIGHT_COUNT];
uniform vec3 pointLightColor[MAX_LIGHT_COUNT];
uniform vec3 pointLightPosition[MAX_LIGHT_COUNT];
uniform vec3 pointLightDecay[MAX_LIGHT_COUNT];
uniform vec3 spotLightColor[MAX_LIGHT_COUNT];
uniform vec3 spotLightDirection[MAX_LIGHT_COUNT];
uniform vec3 spotLightPosition[MAX_LIGHT_COUNT];
uniform vec3 spotLightDecay[MAX_LIGHT_COUNT];
uniform float spotLightAngle[MAX_LIGHT_COUNT];
uniform vec3 materialReflection;
uniform float shininess;
uniform float specularFactor;
varying vec3 vNormal;
varying vec3 vPos;
varying vec3 vCameraPos;

然后实现计算 phong 反射模型的主体逻辑,循环处理每个光源,再计算入射光方向,然后计算漫反射以及镜面反射,最终将结果返回。

float getSpecular(vec3 dir, vec3 normal, vec3 eye) {
  vec3 reflectionLight = reflect(-dir, normal);
  float eyeCos = max(dot(eye, reflectionLight), 0.0);
  return specularFactor *  pow(eyeCos, shininess);
}
vec4 phongReflection(vec3 pos, vec3 normal, vec3 eye) {
  float specular = 0.0;
  vec3 diffuse = vec3(0);
  // 处理平行光
  for(int i = 0; i < MAX_LIGHT_COUNT; i++) {
    vec3 dir = directionalLightDirection[i];
    if(dir.x == 0.0 && dir.y == 0.0 && dir.z == 0.0) continue;
    vec4 d = viewMatrix * vec4(dir, 0.0);
    dir = normalize(-d.xyz);
    float cos = max(dot(dir, normal), 0.0);
    diffuse += cos * directionalLightColor[i];
    specular += getSpecular(dir, normal, eye);
  }
  // 处理点光源
  for(int i = 0; i < MAX_LIGHT_COUNT; i++) {
    vec3 decay = pointLightDecay[i];
    if(decay.x == 0.0 && decay.y == 0.0 && decay.z == 0.0) continue;
    vec3 dir = (viewMatrix * vec4(pointLightPosition[i], 1.0)).xyz - pos;
    float dis = length(dir);
    dir = normalize(dir);
    float cos = max(dot(dir, normal), 0.0);
    float d = min(1.0, 1.0 / (decay.x * pow(dis, 2.0) + decay.y * dis + decay.z));
    diffuse += d * cos * pointLightColor[i];
    specular += getSpecular(dir, normal, eye);
  }
  // 处理聚光灯
  for(int i = 0; i < MAX_LIGHT_COUNT; i++) {
    vec3 decay = spotLightDecay[i];
    if(decay.x == 0.0 && decay.y == 0.0 && decay.z == 0.0) continue;
    vec3 dir = (viewMatrix * vec4(spotLightPosition[i], 1.0)).xyz - pos;
    float dis = length(dir);
    dir = normalize(dir);
    // 聚光灯的朝向
    vec3 spotDir = (viewMatrix * vec4(spotLightDirection[i], 0.0)).xyz;
    // 通过余弦值判断夹角范围
    float ang = cos(spotLightAngle[i]);
    float r = step(ang, dot(dir, normalize(-spotDir)));
    float cos = max(dot(dir, normal), 0.0);
    float d = min(1.0, 1.0 / (decay.x * pow(dis, 2.0) + decay.y * dis + decay.z));
    diffuse += r * d * cos * spotLightColor[i];
    specular += r * getSpecular(dir, normal, eye);
  }
  return vec4(diffuse, specular);
}



最后,在 main 函数中,调用 phongReflection 函数来合成颜色。

void main() {
  vec3 eyeDirection = normalize(vCameraPos - vPos);
  vec4 phong = phongReflection(vPos, vNormal, eyeDirection);
  // 合成颜色
  gl_FragColor.rgb = phong.w + (phong.xyz + ambientLight) * materialReflection;
  gl_FragColor.a = 1.0;
}


下面实战尝试一下:

<!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>如何实现完整的 Phong 反射模型</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)
            // 用于添加和删除光源
            class Phong {
                constructor(ambientLight = [0.5, 0.5, 0.5]) {
                    this.ambientLight = ambientLight;
                    this.directionalLights = new Set();
                    this.pointLights = new Set();
                    this.spotLights = new Set();
                }
                addLight(light) {
                    const {position, direction, color, decay, angle} = light;
                    if(!position && !direction) throw new TypeError('invalid light');
                    light.color = color || [1, 1, 1];
                    if(!position) this.directionalLights.add(light);
                    else {
                        light.decay = decay || [0, 0, 1];
                        if(!angle) {
                            this.pointLights.add(light);
                        } else {
                            this.spotLights.add(light);
                        }
                    }
                }
                removeLight(light) {
                    if(this.directionalLights.has(light)) this.directionalLights.delete(light);
                    else if(this.pointLights.has(light)) this.pointLights.delete(light);
                    else if(this.spotLights.has(light)) this.spotLights.delete(light);
                }
                get uniforms() {
                    const MAX_LIGHT_COUNT = 16; // 最多每种光源设置16个
                    this._lightData = this._lightData || {};
                    const lightData = this._lightData;
                    lightData.directionalLightDirection = lightData.directionalLightDirection || {value: new Float32Array(MAX_LIGHT_COUNT * 3)};
                    lightData.directionalLightColor = lightData.directionalLightColor || {value: new Float32Array(MAX_LIGHT_COUNT * 3)};
                    lightData.pointLightPosition = lightData.pointLightPosition || {value: new Float32Array(MAX_LIGHT_COUNT * 3)};
                    lightData.pointLightColor = lightData.pointLightColor || {value: new Float32Array(MAX_LIGHT_COUNT * 3)};
                    lightData.pointLightDecay = lightData.pointLightDecay || {value: new Float32Array(MAX_LIGHT_COUNT * 3)};
                    lightData.spotLightDirection = lightData.spotLightDirection || {value: new Float32Array(MAX_LIGHT_COUNT * 3)};
                    lightData.spotLightPosition = lightData.spotLightPosition || {value: new Float32Array(MAX_LIGHT_COUNT * 3)};
                    lightData.spotLightColor = lightData.spotLightColor || {value: new Float32Array(MAX_LIGHT_COUNT * 3)};
                    lightData.spotLightDecay = lightData.spotLightDecay || {value: new Float32Array(MAX_LIGHT_COUNT * 3)};
                    lightData.spotLightAngle = lightData.spotLightAngle || {value: new Float32Array(MAX_LIGHT_COUNT)};
                    [...this.directionalLights].forEach((light, idx) => {
                        lightData.directionalLightDirection.value.set(light.direction, idx * 3);
                        lightData.directionalLightColor.value.set(light.color, idx * 3);
                    });
                    [...this.pointLights].forEach((light, idx) => {
                        lightData.pointLightPosition.value.set(light.position, idx * 3);
                        lightData.pointLightColor.value.set(light.color, idx * 3);
                        lightData.pointLightDecay.value.set(light.decay, idx * 3);
                    });
                    [...this.spotLights].forEach((light, idx) => {
                        lightData.spotLightPosition.value.set(light.position, idx * 3);
                        lightData.spotLightColor.value.set(light.color, idx * 3);
                        lightData.spotLightDecay.value.set(light.decay, idx * 3);
                        lightData.spotLightDirection.value.set(light.direction, idx * 3);
                        lightData.spotLightAngle.value[idx] = light.angle;
                    });
                    return {
                        ambientLight: {value: this.ambientLight},
                        ...lightData,
                    };
                }
            }
            // 用于定义物体的材质
            class Material {
                constructor(reflection, specularFactor = 0, shininess = 50) {
                    this.reflection = reflection;
                    this.specularFactor = specularFactor;
                    this.shininess = shininess;
                }
                get uniforms() {
                    return {
                        materialReflection: {value: this.reflection},
                        specularFactor: {value: this.specularFactor},
                        shininess: {value: this.shininess},
                    };
                }
            }
            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 viewMatrix;
                uniform mat4 projectionMatrix;
                uniform mat3 normalMatrix;
                uniform vec3 cameraPosition;
                varying vec3 vNormal;
                varying vec3 vPos;
                varying vec3 vCameraPos;
                void main() {
                    vec4 pos = modelViewMatrix * vec4(position, 1.0);
                    vPos = pos.xyz;
                    // 求光源与点坐标的方向向量
                    vCameraPos = (viewMatrix * vec4(cameraPosition, 1.0)).xyz;
                    vNormal = normalize(normalMatrix * normal);
                    gl_Position = projectionMatrix * pos;
                }
            `;
            // 传入环境光 ambientLight 和材质反射率 materialReflection
            // 片元着色器中计算光线方向与法向量夹角的余弦
            const fragment = `
                precision highp float;
                #define MAX_LIGHT_COUNT 16
                uniform mat4 viewMatrix;
                uniform vec3 ambientLight;
                uniform vec3 directionalLightDirection[MAX_LIGHT_COUNT];
                uniform vec3 directionalLightColor[MAX_LIGHT_COUNT];
                uniform vec3 pointLightColor[MAX_LIGHT_COUNT];
                uniform vec3 pointLightPosition[MAX_LIGHT_COUNT];
                uniform vec3 pointLightDecay[MAX_LIGHT_COUNT];
                uniform vec3 spotLightColor[MAX_LIGHT_COUNT];
                uniform vec3 spotLightDirection[MAX_LIGHT_COUNT];
                uniform vec3 spotLightPosition[MAX_LIGHT_COUNT];
                uniform vec3 spotLightDecay[MAX_LIGHT_COUNT];
                uniform float spotLightAngle[MAX_LIGHT_COUNT];
                uniform vec3 materialReflection;
                uniform float shininess;
                uniform float specularFactor;
                varying vec3 vNormal;
                varying vec3 vPos;
                varying vec3 vCameraPos;
                float getSpecular(vec3 dir, vec3 normal, vec3 eye) {
                    vec3 reflectionLight = reflect(-dir, normal);
                    float eyeCos = max(dot(eye, reflectionLight), 0.0);
                    return specularFactor *  pow(eyeCos, shininess);
                }
                vec4 phongReflection(vec3 pos, vec3 normal, vec3 eye) {
                    float specular = 0.0;
                    vec3 diffuse = vec3(0);
                    // 处理平行光
                    for(int i = 0; i < MAX_LIGHT_COUNT; i++) {
                        vec3 dir = directionalLightDirection[i];
                        if(dir.x == 0.0 && dir.y == 0.0 && dir.z == 0.0) continue;
                        vec4 d = viewMatrix * vec4(dir, 0.0);
                        dir = normalize(-d.xyz);
                        float cos = max(dot(dir, normal), 0.0);
                        diffuse += cos * directionalLightColor[i];
                        specular += getSpecular(dir, normal, eye);
                    }
                    // 处理点光源
                    for(int i = 0; i < MAX_LIGHT_COUNT; i++) {
                        vec3 decay = pointLightDecay[i];
                        if(decay.x == 0.0 && decay.y == 0.0 && decay.z == 0.0) continue;
                        vec3 dir = (viewMatrix * vec4(pointLightPosition[i], 1.0)).xyz - pos;
                        float dis = length(dir);
                        dir = normalize(dir);
                        float cos = max(dot(dir, normal), 0.0);
                        float d = min(1.0, 1.0 / (decay.x * pow(dis, 2.0) + decay.y * dis + decay.z));
                        diffuse += d * cos * pointLightColor[i];
                        specular += getSpecular(dir, normal, eye);
                    }
                    // 处理聚光灯
                    for(int i = 0; i < MAX_LIGHT_COUNT; i++) {
                        vec3 decay = spotLightDecay[i];
                        if(decay.x == 0.0 && decay.y == 0.0 && decay.z == 0.0) continue;
                        vec3 dir = (viewMatrix * vec4(spotLightPosition[i], 1.0)).xyz - pos;
                        float dis = length(dir);
                        dir = normalize(dir);
                        // 聚光灯的朝向
                        vec3 spotDir = (viewMatrix * vec4(spotLightDirection[i], 0.0)).xyz;
                        // 通过余弦值判断夹角范围
                        float ang = cos(spotLightAngle[i]);
                        float r = step(ang, dot(dir, normalize(-spotDir)));
                        float cos = max(dot(dir, normal), 0.0);
                        float d = min(1.0, 1.0 / (decay.x * pow(dis, 2.0) + decay.y * dis + decay.z));
                        diffuse += r * d * cos * spotLightColor[i];
                        specular += r * getSpecular(dir, normal, eye);
                    }
                    return vec4(diffuse, specular);
                }
                void main() {
                    vec3 eyeDirection = normalize(vCameraPos - vPos);
                    vec4 phong = phongReflection(vPos, vNormal, eyeDirection);
                    // 合成颜色
                    gl_FragColor.rgb = phong.w + (phong.xyz + 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 phong = new Phong();
            // 添加一个平行光
            phong.addLight({
                direction: [-1, 0, 0],
            });
            // 添加两个点光源
            phong.addLight({
                position: [-3, 3, 0],
                color: [0.5, 1, 1],
            });
            phong.addLight({
                position: [3, 3, 0],
                color: [1, 0.5, 1],
            });
            // 创建 4 个 matrial 对象,分别对应要显示的四个几何体的材质。
            const matrial1 = new Material(new Color('#fa8072'), 2.0);
            const matrial2 = new Material(new Color('#daa520'), 2.0);
            const matrial3 = new Material(new Color('#2e8b57'), 2.0);
            const matrial4 = new Material(new Color('#6a5acd'), 2.0);
            const program1 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ...matrial1.uniforms,
                    ...phong.uniforms,
                },
            });
            const program2 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ...matrial2.uniforms,
                    ...phong.uniforms,
                },
            });
            const program3 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ...matrial3.uniforms,
                    ...phong.uniforms,
                },
            });
            const program4 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ...matrial4.uniforms,
                    ...phong.uniforms,
                },
            });
            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


Phong 反射模型的局限性

Phong 反射模型没有考虑物体反射光对其他物体的影响,也没有考虑物体对光线遮挡产生的阴影。

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

热门文章

最新文章

相关课程

更多
下一篇
DataWorks