视觉高级篇24 # 如何模拟光照让3D场景更逼真?(下)
Posted 凯小默
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了视觉高级篇24 # 如何模拟光照让3D场景更逼真?(下)相关的知识,希望对你有一定的参考价值。
说明
【跟月影学可视化】学习笔记。
什么是镜面反射?
如果若干平行光照射在表面光滑的物体上,反射出来的光依然平行,这种反射就是镜面反射。越光滑的材质,它的镜面反射效果也就越强,并且物体表面会有闪耀的光斑,也叫镜面高光。
镜面反射的性质:入射光与法线的夹角等于反射光与法线的夹角。
如何实现有向光的镜面反射?
实现镜面反射效果的步骤:
- 求出反射光线的方向向量
- 根据相机位置计算视线与反射光线夹角的余弦
- 使用系数和指数函数设置镜面反射强度
- 将漫反射和镜面反射结合起来,让距离光源近的物体上形成光斑
下面以点光源为例来实现光斑:
<!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>
什么是 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 ^ \\hatL_m Lm^:物体表面某点指向光源m的位置的向量
- N ^ \\hatN N^:物体表面某点的法线
- R m ^ \\hatR_m Rm^:光源在物体表面某点发生镜面反射的方向
- V ^ \\hatV 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以上是关于视觉高级篇24 # 如何模拟光照让3D场景更逼真?(下)的主要内容,如果未能解决你的问题,请参考以下文章