WebGL - 光照原理

本文深入探讨WebGL中的光照原理,涵盖平行光、点光源和环境光的类型,重点解析漫反射和环境反射的计算方法,以及在不同光照条件下如何实现运动物体的光照效果。通过多个示例程序展示从平行光下的漫反射到逐片元光照的实现过程。
摘要由CSDN通过智能技术生成

1、光照原理

现实世界中,依据光源和方向,光源照射到物体上时,发生了两个重要的现象;

  • 物体不同的表面明暗程度不一致;
  • 物体向地面投下了影子;

三围图形学术语中 着色的真正含义是:根据光照条件重建 “物体各表面明暗不一的效果”的过程,着色器最初被发明出来就是为了重建光照产生的明暗现象;

在讨论着色之前需要考虑一下两件事:

  • 发出光线的光源的类型;
  • 物体表面如何反射光线;

2、光源类型

平行光:光线是平行的,并且具有方向,需要指定方向和颜色

点光源光:是从一个点向周围的所有方向发出的光,需要指定位置和颜色

环境光:指那些经光源(点光或平行光)发出后,被墙壁等物体多次反射,然后照到物体上的光;

环境光,从各个角度照射物体,其强度都是一致的,环境光不需要指定位置和方向,只需要指定颜色即可;

3、反射类型

物体向哪个方向反射光,反射光是什么颜色,取决于以下两个因素;

  • 入射光物体表面的类型

入射光的信息包括入射光的方向和颜色,物体表面的信息包括表面固有的颜色,也称基底色和反射特性;

物体表面反射光线有两种方式:

  • 漫反射
  • 环境反射

1、漫反射

漫反射是针对平行光和点光源而言的,漫反射的反射光在各个方向上是均匀的,如果物体表面像镜子一样光滑,那么光线就会以特定的角度反射出去,但现实中大部分材质都是粗糙的,因此在这种情况下,反射光酒不会以不固定的角度反射出去;

反射光的颜色取决于:入射光的颜色、表面的基底色、入射光与表面形成的入射角;

<漫反射光颜色> = <入射光颜色> * <表面基底色> * cosθ

上面式子中,<入射光颜色>指的是点光源或者平行光的颜色,乘法操作时在颜色矢量上逐分量RGB进行的,因为漫反射光在各个方向上都是均匀的,所以从任何角度看上去强度都是相等的;

2、环境反射

环境反射式针对环境光而言的,在环境反射中,反射光的方向可以认为就是入射光的反方向,由于环境光照射物体的方式就是个方向均匀、强度相等的,所以反射光也是各项均匀的;

<环境反射光颜色> = <入射光颜色> * <表面颜色>

上面的入射光颜色,就是环境光的颜色

当漫反射和环境反射同时存在时,将两者加起来,就会得到物体最终被观察到的颜色;

<表面的反射光颜色> = <漫反射光颜色> + <环境反射光颜色>

4、平行光下的漫反射

漫反射的反射光,其颜色与如入射光在入射点的入射角θ有关,平行光的方向式唯一的,对于一个平面上的所有点,入射角式相同的;

物体的表面基底色是“物体本来的颜色”,或者说是“物体在标准白光下的颜色”;

通过下面的式子可以计算反射的颜色

<漫反射光颜色> = <入射光颜色> * <表面基底色> * cosθ

1、根据光线和表面的方向计算入射角

在程序中,我们无法直接指定入射角θ为多少度,需要根据入射光的方向和物体表面的朝向(即法线方向)来计算入射角;

通过计算两个矢量的点积,来计算这两个矢量的夹角余弦值,cosθ,点积运算GLSL ES内置了点积运算函数;

<漫反射颜色> = <入射光颜色> * <表面基底色> * (<光线方向> . <法线方向>)

有以下两点需要注意:

  • 光想方向矢量和表面法向矢量的长度必须为1,否则反射光的颜色就会过暗或过亮;
  • 光线方向实际上就是入射方向的反方向,即从入射点指向光源方向,该方向与法线方向的夹角才是入射角;

2、法线

物体表面的朝向,即垂直于表面的方向,又称为法线或者法向量,法向量有三个分量,(nx,ny,nz)表示从原点(0, 0, 0)指向点(nx,ny,nz)的方向,

一个表面具有两个法向量

每个表面都有两个面,正面和背面,即两个面各具有一个法向量;

平面的法向量的唯一性

由于法向量表示的是方向,与位置无关,所以一个平面只有一个法向量,换句话说平面的任意一点都具有相同的法向量,法向量与位置无关;

5、示例程序

顶点着色器

attribute vec4 a_Position;
attribute vec4 a_Color;
attribute vec4 a_Normal;// 法向量
uniform mat4 u_MvpMatrix;// 模型视图投影矩阵
uniform vec3 u_LightColor;// 光线颜色
uniform vec3 u_LightDirection;//光线位置,归一化世界坐标
varying vec4 v_Color;
void main(){
    gl_Position = u_MvpMatrix * a_Position;
    // 对法向量进行归一化
    vec3 normal = normalize(vec3(a_Normal));
    // 计算光线方向和法向量的点积
    float nDotL = max(dot(u_LightDirection, normal), 0.0);
    // 计算反射光的颜色
    vec3 diffuse = u_LightColor * vec3(a_Color) * nDotL;
    // 颜色值就是漫反射的颜色
    v_Color = vec4(diffuse, a_Color.a);
}

片元着色器只负责接收颜色值

...
varying vec4 v_Color;
void main(){
    gl_FragColor = v_Color;
}

javaScript程序流程

//...
var u_LightColor = gl.getUniformLocation(gl.program, 'u_LightColor');
var u_LightDirection = gl.getUniformLocation(gl.program,'u_LightDirection');
...

// 设置光线颜色
gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0);
// 设置光线方向,世界坐标系下的
var lightDirection = new Vector3([0.5, 3.0, 4.0]);
lightDirection.normalize();//归一化
gl.uniform3fv(u_LightDirection, lightDirection.elements);

示例1.平行光漫反射下的立方体

6、环境光下的漫反射

上个示例我们看到,灯光没有照到立方体的面都是黑色,真实情况下就算灯光照不到,立方体也会有一点泛白色,这是因为没有添加环境光的原因。

环境光的颜色取决于 光的颜色物体表面的颜色

<环境反射光颜色> = <入射光颜色> * <表面基底色>

此时再加上漫反射光的颜色,就是表面的反射光的颜色

<表面的反射光的颜色> = <漫反射光的颜色> + <环境反射光颜色>

着色器部分代码

...
uniform vec3 u_LightColor;// 光线颜色
uniform vec3 u_LightDirection;//归一化的世界坐标
uniform vec3 u_AmbientLight;// 环境光颜色
...
void main(){
    ...
    // 计算环境光产生的反射光颜色
	vec3 ambient = u_AmbientLight * a_Color.rgb;
    // 将以上两者相加得到物体最终的颜色
    v_Color = vec4(diffuse + ambient, a_Color.a);
}

示例2.添加环境光后的效果

查看以上示例,可以看到此时立方体的表面不是那么黑了,但是当立方体旋转的时候,灯光部分是不跟着变化的,这是因为立方体上的法线并没有随着模型旋转的旋转而变换,物体平移、缩放、旋转都可以用坐标变换来表示,因此物体运动会改变每个表面的法向量,从而导致光照效果发生变化;

7、运动物体光照效果

立方体的坐标变换,会使每个表面的法向量也变化,但时平移变换不会改变法向量,旋转会,因为旋转改变了物体的方向;

1、逆转置矩阵

对顶点进行变换的矩阵称为 模型矩阵,所以变换之后的法向量就是变换之前的法向量乘以模型矩阵的 逆转置矩阵

逆转置矩阵:逆矩阵的转置

逆矩阵的含义,如果矩阵M的逆矩阵是R,那么R * M或者 M*R 的结果都是单位阵,转置的意思是,将矩阵的行列进行调换,看上去就像是沿着 左上 - 右下对角线进行了翻转

规则:用法向量乘以模型矩阵的逆转置矩阵,就可以求得变换后的法向量

步骤:1、求原矩阵的逆矩阵,2、将上一步求得的逆矩阵进行转置

webgl编程指南》作者提供了一个Matrix4对象,提供了以下的对矩阵进行操作的方法

方法描述
Matrix4.setInverseof(m)使自身(调用本方法的Matrix4类型的实例)成为矩阵m的逆矩阵
Matrix4.transpose()对自身进行转置操作,并将自身设为转置后的结果
var normalMatrix = new Matrix4();

// 根据模型矩阵计算用来变换法向量的矩阵
normalMatrix.setInverseof(modelMatrix);
normalMatrix.transpose();

修改示例二效果如下

示例3.通过模型变换的逆转置矩阵,使法向量变换

8、点光源光

与平行光相比,点光源发出的光,在三维空间中的不同位置上其方向也不同,所以在对点光源下的物体进行着色时,需要字每个入射点计算点光源光咋该处的方向;

前一节根据每个顶点的法向量,和平行光入射方向来计算反射光的颜色,但是点光源的方向不再是恒定不变的,需要根据每个顶点的位置逐一计算,着色器需要知道点光源自身的所在位置,而不是光的方向;

顶点着色器代码如下,片元着色器不变

attribute vec4 a_Position;
attribute vec4 a_Color;
attribute vec4 a_Normal;
uniform mat4 u_MvpMatrix;//法向量
uniform mat4 u_ModelMatrix;// 模型矩阵
uniform mat4 u_NormalMatrix;// 用来变换法向量的矩阵
uniform vec3 u_LightPosition;// 光源位置 归一化世界坐标
uniform vec3 u_AmbientLight;// 环境光颜色
varying vec4 v_Color;
void main(){
    // 经过矩阵变换后的顶点坐标
    gl_Position = u_MvpMatrix * a_Position;
    // 计算后的法向量并归一化
    vec3 normal = normalize(vec3(u_NormalMatrix * a_Normal))...
}

示例4.点光源效果

9、逐片元光照

上面的灯光案例都没有用到片元着色器,其实上面的灯光颜色时经过内插出来的,因此会看着灯光效果还是有点差距,为了使效果更加逼真,因此需要对每一个都计算光照效果;

要在表面的每一点上计算光照产生的颜色,需要逐片元的进行计算,因此需要调用片元着色器;

// 顶点着色器
    var v_shader_source = '' +
        'attribute vec4 a_Position;' +
        'attribute vec4 a_Normal;' +
        'uniform mat4 u_MvpMatrix;' +
        'uniform mat4 u_ModelMatrix;' +// 模型矩阵
        'uniform mat4 u_NormalMatrix;' +// 法向量旋转矩阵
        'varying vec4 v_Color;' +
        'varying vec3 v_Normal;' +
        'varying vec3 v_Position;' +
        'void main(){' +
        '   vec4 color = vec4(1.0, 1.0, 1.0, 1.0);' + // 物体颜色
        '   gl_Position = u_MvpMatrix * a_Position;' +
        '   v_Position = vec3(u_ModelMatrix * a_Position);' +
        '   v_Normal = normalize(vec3(u_NormalMatrix * a_Normal));' +
        '   v_Color = color;' +
        '}';
    // 片元着色器
    var f_shader_source = '' +
        'precision mediump float;' +
        'uniform vec3 u_LightColor;' + // 灯光颜色
        'uniform vec3 u_LightPosition;' + // 灯光位置
        'uniform vec3 u_AmbientLight;' + // 环境光颜色
        'varying vec3 v_Normal;' +
        'varying vec3 v_Position;' +
        'varying vec4 v_Color;' +
        'void main(){' +
        // 对法线进行归一化,因为其内插之后长度不一定是1.0
        '   vec3 normal = normalize(v_Normal);' +
        // 计算光线方向并归一化
        '   vec3 lightDirection = normalize(u_LightPosition - v_Position);' +
        // 计算光线方向和法向量的点积
        '   float nDotL = max(dot(lightDirection, normal), 0.0);' +
        // 计算漫反射和环境反射以及最终的颜色值
        '   vec3 diffuse = u_LightColor * v_Color.rgb * nDotL;' +
        '   vec3 ambient = u_AmbientLight * v_Color.rgb;' +
        '   gl_FragColor = vec4(diffuse + ambient, v_Color.a);' +
        '}';

示例5.逐片元渲染效果

此时查看一下示例,对比上一个示例你会发现,光照的渐变效果显得更加自然;

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值