用JavaScript玩转计算机图形学(二)基本光源
上一篇介绍了简单的光线追踪,凑合了临时用的光源去渲染效果。这次将讲解三种基本光源,及一些背景理论。过分简化的教材和现成API(OpenGL/Direct3D等)可能会做成一些错误理解。在此,希望文章能简单之余,又不失背后理论。读者明白之后,可把概念简化,或按实际情况调整。
本文代码可在此下载(10KiB)。
读者若喜欢本文,可按推荐按钮以示鼓励。如果写得不够清楚,或有错误之处,可留言相告。
光
在物理上,光(light)可以视为电磁波(electromagnetic wave)或光子(photon)。在计算机图形学的领域里,通常只会用到光的部份物理性质,例如假设光是直线前进(不受因引力影响),忽略光的速度,通常不考虑衍射(diffraction)、干涉(interference )等等(好吧,也不考虑量子行为☺)。因为,计算机图形学不是物理学,最终目标(笔者认为)只是要渲染视觉上美的事物,只要模拟到某个合适层次的模型,有时候还为了美观而采用非物理/非真实的方式。
方向光源
光源(light source)放射(emit)光,而非散射(scatter)或吸收(absorb)光。
最简单的光源模型,是方向光源(directional light),又称平行光源。这种光源假设光在无限远放射,在任何位置,放射方向都是一致的,可以模拟类似太阳的光线(虽然实际上太阳并非无限远)。
方向光源的方向,通常用光向量(light vector)去表示。为方便计算,通常是单位向量,并且和光的放射方向相反
方向光源的另一个属性,是指定其照明的量。量度光的科学叫幅射度量学(radiometry),本文暂且略过其细节。这里只用到光的其中一个量度方式,就是每秒通过每单位面积平面的光子总能量,称为幅照度(irradiance)。
光的颜色,是由不同频率的光波及其频谱,在人类视觉上形成的。详细内容又涉及光度测定(photometry)、比色法(colorimetry)、视觉感知(visual perception)、甚至哲学等,有机会再谈。这里只使用常见的红绿蓝三个颜色通道(color channel)。光源的幅照度也可以用这三通道来描述,因此,仍可用前文的Color类来描述幅照度。但注意,光的幅照度范围是零到无限大,并不是[0,1]或[0,255]。光的"颜色"和材质的"颜色"并非同一个概念,关于这点,读者可思考以下一个简单命题
客观上,有接近白色的纸,但没有白色的光
关于这个命题,和材质的"颜色",将于下回分解。
阴影
一个光源的阴影(shadow),是因不透明障碍物,以致其不能到达的地方。我们可使用已有的几何相交功能,去检测某一位置,在方向上有否障碍物。光源追踪方法在阴影处理上很简单,光删化方法就复杂得多。
实现DirectionalLight类
在编程时,需要为不同种类的光源设计一个共通接口。渲染器要从光源取得,在某个空间位置,其光向量和幅照度。在此,定义光源有一成员函数sample(scene, position),并传回一个LightSample对象:
1
2
|
LightSample = function (L, EL) { this .L = L; this .EL = EL; };
LightSample.zero = new LightSample(Vector3.zero, Color.black);
|
以下是方向光源的代码,预设使用阴影:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
DirectionalLight = function (irradiance, direction) { this .irradiance = irradiance; this .direction = direction; this .shadow = true ; };
DirectionalLight.prototype = {
initialize: function () { this .L = this .direction.normalize().negate(); },
sample: function (scene, position) {
// 阴影测试
if ( this .shadow) {
var shadowRay = new Ray3(position, this .L);
var shadowResult = scene.intersect(shadowRay);
if (shadowResult.geometry)
return LightSample.zero;
}
return new LightSample( this .L, this .irradiance);
}
};
|
渲染幅照度
sample()函数可以传回相对光向量的幅照度,但物体表面并不一定垂直于光向量。光源越接近平面,每面积接受的能量就越少。可以想像太阳在中午是最亮的,日出日落时是最暗的。如下图所示,平面法向量方向的面积,是光向量方向的面积的倍。
因此,设光源的光向量方向幅照度为,平面接收到的幅照度为
幅照度是能量,可以累加,所以多个光源下,平面接收到的总幅照度为
以下的简单代码,测试一个方向光源在场境中的总幅照度:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
function renderLight(canvas, scene, lights, camera) {
// 从canvas取得imgdata和pixels,跟之前的代码一样
// ...
scene.initialize();
for ( var k in lights)
lights[k].initialize();
camera.initialize();
var i = 0;
for ( var y = 0; y < h; y++) {
var sy = 1 - y / h;
for ( var x = 0; x < w; x++) {
var sx = x / w;
var ray = camera.generateRay(sx, sy);
var result = scene.intersect(ray);
if (result.geometry) {
var color = Color.black;
for ( var k in lights) {
var lightSample = lights[k].sample(scene, result.position);
if (lightSample != lightSample.zero) {
var NdotL = result.normal.dot(lightSample.L);
// 夹角小约90度,即光源在平面的前面
if (NdotL >= 0)
color = color.add(lightSample.EL.multiply(NdotL));
}
}
pixels[i] = color.r * 255;
pixels[i + 1] = color.g * 255;
pixels[i + 2] = color.b * 255;
pixels[i + 3] = 255;
}
i += 4;
}
}
ctx.putImageData(imgdata, 0, 0);
}
|
Run
修改代码试试看
|
点光源
点光源/点光灯(point light),又称全向光源/泛光源/泛光灯(omnidirectional light/omni light),是指一个无限小的点,向所有光向平均地散射光。
其光向量,就是表面位置往点光源位置的方向:
学习物理时,经常有这种往所有方向发射的情况(例如引力、声音等)。类比可知,接收到的能量和距离的关系,是成平方反比定律的:
当中I为幅射强度(intensity, radiant intensity),当r=1时,幅射强度和幅照度相等。
通常称为衰减(attenuation)系数。有时候会为各种需求,写一些非物理正确的衰减系数。
实现PointLight类
以下代码中,不直接使用normalize(),令r和其平方可以在之后分别使用,算是简单的优化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
PointLight = function (intensity, position) { this .intensity = intensity; this .position = position; this .shadow = true ; };
PointLight.prototype = {
initialize: function () { },
sample: function (scene, position) {
// 计算L,但保留r和r^2,供之后使用
var delta = this .position.subtract(position);
var rr = delta.sqrLength();
var r = Math.sqrt(rr);
var L = delta.divide(r);
// 阴影测试
if ( this .shadow) {
var shadowRay = new Ray3(position, L);
var shadowResult = scene.intersect(shadowRay);
// 在r以内的相交点才会遮蔽光源
if (shadowResult.geometry && shadowResult.distance <= r)
return LightSample.zero;
}
// 平方反比衰减
var attenuation = 1 / rr;
// 计算幅照度
return new LightSample(L, this .intensity.multiply(attenuation));
}
};
|
Run
修改代码试试看
|
聚光灯
现实中,并不存在理想的点光源,放射的光在不同方向是有差异的。聚光灯(spot light)是常用的一种模式,它在点光源的基础上,加入圆锥形的范围。聚光灯可以有不同的模型,以下采用Direct3D固定功能管道(fixed-function pipeline)用的模型做示范。
聚光灯有一个主要方向s,再设置两个圆锥范围,称为内圆锥和外圆锥,两圆锥之间的范围称为半影(penumbra)。内外圆锥的内角分别为和。聚光灯可计算一个聚光灯系数,范围为[0,1],代表某方向的放射比率。内圆锥中系数为1(最亮),内圆锥和外圆锥之间系数由1逐渐变成0。另外,可用另一参数p代表衰减(falloff),决定内圆锥和外圆锥之间系数变化。方程式如下:
实现SpotLight类
SpotLight类只是多了那几个参数,以计算聚光灯系数,最后结合到幅照度。很多参数可在initialize()里预计算,减少在sample()里重复运算。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
SpotLight = function (intensity, position, direction, theta, phi, falloff) {
this .intensity = intensity;
this .position = position;
this .direction = direction;
this .theta = theta;
this .phi = phi;
this .falloff = falloff;
this .shadow = true ;
};
SpotLight.prototype = {
initialize: function () {
this .S = this .direction.normalize().negate();
this .cosTheta = Math.cos( this .theta * Math.PI / 180 / 2);
this .cosPhi = Math.cos( this .phi * Math.PI / 180 / 2);
this .baseMultiplier = 1 / ( this .cosTheta - this .cosPhi);
},
sample: function (scene, position) {
// 计算L,但保留r和r^2,供之后使用
var delta = this .position.subtract(position);
var rr = delta.sqrLength();
var r = Math.sqrt(rr);
var L = delta.divide(r);
// 计算聚光灯因子
var spot;
var SdotL = this .S.dot(L);
if (SdotL >= this .cosTheta)
spot = 1;
else if (SdotL <= this .cosPhi)
spot = 0;
else
spot = Math.pow((SdotL - this .cosPhi) * this .baseMultiplier, this .falloff);
// 阴影测试
if ( this .shadow) {
var shadowRay = new Ray3(position, L);
var shadowResult = scene.intersect(shadowRay);
// 在r以内的相交点才会遮蔽光源
if (shadowResult.geometry && shadowResult.distance <= r)
return LightSample.zero;
}
// 平方反比衰减
var attenuation = 1 / rr;
// 计算幅照度
return new LightSample(L, this .intensity.multiply(attenuation * spot));
}
};
|
Run
修改代码试试看
|
例子
三原色
这个例子把三原色聚光灯重叠射度地板,可以看到它们的颜色混合。
Run
修改代码试试看
|
很多光源
这个例子在天花加了36个点光源,和一个从后往前的填充用方向光源。有时候灯光师会加入填充光源(fill light),去加强对象的轮廓及立体感(有时候用上冷暖色的对比)。这个渲染比较慢,可能要半分钟啊!
Run
修改代码试试看
|
结语
本文简单介绍了三种基本的光源,这些光源除了应用在光线追踪渲染器上,也常用在光栅化渲染器中。
除这三种以外,还有一类比较高阶的光源──面光源(area light)。面光源比这三种光源更真实,也能完美地做到真实的柔和阴影。如果能实现面光源,基本上也不用特定做「光源」这种类,取而代之,可以设定某些材质本身能发光即可。当然,没有免费午餐,随之而来的时间复杂度也增加。
有了光源,下一篇大概会开始谈材质,讲述光源和材质间的互动。
参考
- Tomas Möller, Eric Haines, Naty Hoffman, Real-time Rendering 3rd Edition, AK Peters 2008
- Matt Pharr, Greg Humphreys, Physically Based Rendering, Morgan Kaufmann, 2004
用JavaScript玩转计算机图形学(二)基本光源相关推荐
- 用JavaScript玩转计算机图形学(一)光线追踪入门
系列简介 记得小时候读过一本关于计算机图形学(computer graphics, CG)的入门书,从此就爱上了CG.本系列希望,采用很多人认识的JavaScript语言去分享CG,令更多人有机会接触 ...
- 计算机图形学二维变换知识点,计算机图形学 二维变换及二维.ppt
计算机图形学 二维变换及二维 第三章 二维变换及二维观察 本章主要内容 3.1二维图形的基本变换 3.2窗口视图变换 3.3复合变换 3.4二维图形裁剪 3.5本章小结 3.1 二维图形的基本变换 3 ...
- 计算机图形学二维图形基本变换实验原理,计算机图形学实验:二维图形变换.docx...
计算机图形学实验:二维图形变换.docx (9页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 19.90 积分 实验三 二维图形变换一.实验任务1. 通 ...
- 计算机图形学-二维图形变换 笔记总结与代码实战
文章目录 1.向量基础知识 2.图形坐标系 3.二维图形变换原理 4.二维图形几何变换 5.窗口视区变换 基本二维几何变换代码 二维复合变换实战-五星红旗绘制 1.向量基础知识 为什么向量如此重要:在 ...
- 计算机图形学二维图形基本变换实验原理,江苏大学-计算机图形学第三次实验报告-二维图形变换...
<江苏大学-计算机图形学第三次实验报告-二维图形变换>由会员分享,可在线阅读,更多相关<江苏大学-计算机图形学第三次实验报告-二维图形变换(13页珍藏版)>请在人人文库网上搜索 ...
- 计算机图形学——二维图形几何转换
文章目录 一.实验目的 二.实验要求 三.实验步骤 1.打开直线扫面转换MFC项目工程,及其中的直线类CLine. 2.二维点类CP2添加齐次坐标 3.设计实现二维图像几何变换类 1.新建二维图形几何 ...
- 计算机图形学——二维图形变换裁剪
算法描述 1.基本变换矩阵 (1).缩放矩阵 void ScaleMatrix(float Sx, float Sy, float m[3][2]) {for (int i = 0; i < 3 ...
- 计算机图形学--二维图形变换
目录 概述 平移 对称 旋转 错切 缩放 概述 对于二维图形来讲,所有基本变换均可以通过确定图形的点的平移.对称.旋转.错切以及缩放几种变换组合得到,而在上述几种变换中,除对称变换外,其余变换均可通过 ...
- 计算机图形学-二维图形-几何变换
几何变化 一.概述 图形变换:是一种几何变换,在二维图形处理过程中,常常需要对平面图形的形状,尺寸,显示方向和显示位置进行修改,来达到改变图形的目的. 几何变换:是一种先行变换,对原来图形中的一点坐标 ...
最新文章
- R语言XML格式数据导入与处理
- C++实现拓扑排序(vector模拟邻接表存储,栈实现)
- matlab中inf函数,matlab中voronoin()函数的用法,求高手指点
- 怎么判断一个机器可以跑多少用户和并发_美逛微信云发单机器人申请方法及其问题解答汇总...
- Java新特性之Nashorn的实例详解
- mysql创建新用户并设置密码时报错:Your password does not satisfy the current policy requirements
- 方差缩减——分层抽样
- 【猿人学WEB题目专解】猿人学第5题
- 使用钢琴键盘作为电脑键盘[关闭]
- 使用u-view上传图片
- linux自带的二进制查看器
- MySQL中的真实案例:某网站有email字段,存储邮件地址,想调查163,126,qq,eyou邮箱的比例;
- 小米2s 怎样离线安装Kali Linux
- 超详细!关于万能头文件<bits/stdc++.h>的细节
- linux创建用户到指定分区,2016年Linux认证考试试题及答案
- 优信拍集团php面试题_优信拍二手车网登陆人民日报, 优信二手车怎么样优信二手车靠谱吗? 利好利空...
- 【爬虫系列】Python 爬虫入门(1)
- 2012年2月上旬我国网络不良与垃圾信息分析报告
- 转: Ubuntu 中文 Wiki
- 动感单车花式动作123
热门文章
- 评分卡建模—拒绝推断
- nginx下部署vue项目
- 朱宁:70%散户跑不赢大盘?因为机构利用了他们的错误
- 程序化广告交易中的点击率预估
- 鸿蒙45000,华为发布鸿蒙系统沟通口径通知 网友表示支持国货
- Linux 如何获取PAGE size的大小?
- 深入理解分布式技术 - 漫谈分布式事务及解决方案
- JVM-09自动内存管理机制【内存分配和回收策略】
- 【Linux】【服务器】 CentOS7下远程访问mysql数据库_创建用户及授予权限_查看用户、修改密码详细步骤
- c语言考试长沙理工大学,2013年长沙理工大学C语言考试试卷A.doc