说明

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

什么是镜面反射?

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

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

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

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

  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 Libraryimport * 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 以及材质反射率 materialReflectionconst 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>

什么是 Phong 反射模型?

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

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

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

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


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

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

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

光源部分:

  • 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、定义光源模型

属性(作用) /光源 点光源 平行光 聚光灯
direction 方向 (定义光照方向)
position 位置 (定义光源位置)
color 颜色 (定义光的颜色)
decay 衰减 (光强度随着距离而减小)
angle 角度 (光传播的角度范围)

实现定义一个 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 Libraryimport * 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 16uniform 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 以及材质反射率 materialReflectionconst 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>

效果如下:

Phong 反射模型的局限性

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

参考资料

  • Phong光照模型
  • 百科:Phong光照模型
  • 百科:补色渲染

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

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

    说明 [跟月影学可视化]学习笔记. 光照效果简介 物体的光照效果是由光源.介质(物体的材质)和反射类型决定的,而反射类型又由物体的材质特点决定. 在 3D 光照模型中,根据不同的光源特点分为四种: 环 ...

  2. 【视觉高级篇】27 # 如何实现简单的3D可视化图表:GitHub贡献图表的3D可视化?

    说明 [跟月影学可视化]学习笔记. 第一步:准备要展现的数据 可以使用这个生成数据:https://github.com/sallar/github-contributions-api 这里直接使用月 ...

  3. 【视觉高级篇】20 # 如何用WebGL绘制3D物体?

    说明 [跟月影学可视化]学习笔记. 如何用 WebGL 绘制三维立方体 我们知道立方体有8个顶点,6个面,在 WebGL 中,需要用 12 个三角形来绘制它.把每个面的顶点分开,需要 24 个顶点. ...

  4. 【视觉高级篇】25 # 如何用法线贴图模拟真实物体表面

    说明 [跟月影学可视化]学习笔记. 什么是法线贴图? 法线贴图就是在原物体的凹凸表面的每个点上均作法线,通过RGB颜色通道来标记法线的方向,你可以把它理解成与原凹凸表面平行的另一个不同的表面,但实际上 ...

  5. 【视觉高级篇】21 # 如何添加相机,用透视原理对物体进行投影?

    说明 [跟月影学可视化]学习笔记. 如何理解相机和视图矩阵? 用一个三维坐标(Position)和一个三维向量方向(LookAt Target)来表示 WebGL 的三维世界的一个相机.要绘制以相机为 ...

  6. 【视觉高级篇】19 # 如何用着色器实现像素动画?

    说明 [跟月影学可视化]学习笔记. 如何用着色器实现固定帧动画 <!DOCTYPE html> <html lang="en"><head>&l ...

  7. 【视觉高级篇】22 # 如何用仿射变换来移动和旋转3D物体?

    说明 [跟月影学可视化]学习笔记. 三维仿射变换:平移 对于平移变换来说,如果向量 P( x 0 ​ x_0​ x0​​, y 0 y_0 y0​​, z 0 ​ z_0​ z0​​) 沿着向量 Q( ...

  8. 【视觉高级篇】18 # 如何生成简单动画让图形动起来?

    说明 [跟月影学可视化]学习笔记. 动画的三种形式 固定帧动画:预先准备好要播放的静态图像,然后将这些图依次播放,实现起来最简单,只需要为每一帧准备一张图片,然后循环播放即可. 增量动画:就是在每帧给 ...

  9. 谷粒商城--订单服务--高级篇笔记十一

    1.页面环境搭建 1.1 静态资源导入nginx 等待付款 --------->detail 订单页 --------->list 结算页 --------->confirm 收银页 ...

最新文章

  1. Ghost配置1——删除社交Link
  2. 用C语言扩展Python的功能的实例
  3. 漫画:别人的35岁!
  4. Django(part28)--F对象
  5. sqoop、datax几种导入导出
  6. 高性能JavaScript-JS脚本加载与执行对性能的影响
  7. 用html5做一个介绍自己家乡的页面_厚溥资讯 | HTML5的小知识点小集合(上)
  8. 《当代教育心理学》(第2版)学习笔记
  9. 基于WASAPI的录音播音系统
  10. Python爬虫:新浪新闻详情页的数据抓取(函数版)
  11. php 增加空行,php 替换空行 不匹配空行
  12. 【汇编】微机原理与接口技术课程设计
  13. sony android mp3播放器,高音质与流媒体兼具,索尼 NW-ZX500 安卓音乐播放器评测
  14. 使用编译版rtklib过程中如何进行main的调试(spp ppp)
  15. UML 有几种关系图标
  16. 微信小程序:身份证号码+手机号校验
  17. vue-fullcalendar 日历插件
  18. 索尼美能达50微-版本区别及实拍测评(sony/minolta)50 f2.8 macro
  19. UE4 第三人称人物 目标偏移(Aim offset)学习笔记
  20. 中国顶尖“黑客”团队:一半是历年高考状元

热门文章

  1. IIS10(WinServer2019自带)如何限制网站的流量带宽
  2. iPhone 的序列号「Serial No.」、UDID、IMEI、ICCID 分别是什么意思
  3. 天载杠杆炒股三大股指探底回升
  4. 在MacDown中插入本地图片和GitHub图片
  5. 盛业首席战略官原野:产业数字化时代,连接比拥有更重要
  6. c语言中二次函数复数根,初中二次函数求根公式是什么
  7. 数学分析_导函数连续问题分析
  8. 测试人跳槽!怎么说离职原因新的公司比较能接受?
  9. MeeGo的创新模式-(讨论MeeGo与Android的比较和选择)
  10. 圣彼得堡大帝理工学院有没有计算机专业,圣彼得堡彼得大帝理工大学