学习WebGL之基本光照

本系列所有文章目录

下面是本文例子的运行截图,可以前往我的博客查看代码演示。

本文主要介绍如何使用Shader实现平行光的效果。什么是平行光呢?我们可以拿激光做比喻,平行光的方向不会随着离光源的距离而改变。所以我们在模拟平行光的时候仅仅需要使用一个光照方向即可。
我们有了光照方向,接下来还需要一个重要数据,平面的朝向。一个平面如果刚好面朝光线,那自然是最亮的。当然还有些材质的平面可以反射光线,反射光线的强度和你观察的角度相关,不过这些本文都不会介绍。后面会有专门一篇介绍复杂的光照模型。
我们用法线向量来表示平面朝向,在具体实现中,每个点都会有一个法线向量。所谓法线向量就是垂直于平面的一个三维向量,如下图所示。


图中展示了两种法线向量的表示方法,左边是每个多边形的每个点有一个法线向量,右边是每个点有一个法线向量,共享点的法线向量是这个点在所有平面上的法线向量之和。法线向量应该总是被规范化成单位向量。本文的例子中使用的是左边的方式。

如果你对向量相关的知识不是很了解,可以参考百度百科

有了法线向量和光照方向之后,只要将它们相乘即可得到光照强度。接下来开始分析代码。

两个单位向量相乘,结果是cos(向量夹角),夹角越大,cos(向量夹角)越小,刚好符合前面说的规律。

首先我们来看Vertex Shader。

attribute vec4 position;
attribute vec3 normal;

varying vec3 fragNormal;

uniform float elapsedTime;
uniform mat4 projectionMatrix;
uniform mat4 cameraMatrix;
uniform mat4 modelMatrix;
void main() {
    fragNormal = normal;
    gl_Position = projectionMatrix * cameraMatrix * modelMatrix * position;
}

我添加了法线向量属性attribute vec3 normal;。然后将法线向量传递给Fragment ShaderfragNormal = normal;

接下来是Fragment Shader。

precision highp float;

varying vec3 fragNormal;

uniform float elapsedTime;
uniform vec3 lightDirection;
uniform mat4 normalMatrix;

void main(void) {
    vec3 normalizedLightDirection = normalize(-lightDirection);
    vec3 transformedNormal = normalize((normalMatrix * vec4(fragNormal, 1.0)).xyz);

    float diffuseStrength = dot(normalizedLightDirection, transformedNormal);
    diffuseStrength = clamp(diffuseStrength, 0.0, 1.0);
    vec3 diffuse = vec3(diffuseStrength);

    vec3 ambient = vec3(0.3);

    vec4 finalLightStrength = vec4(ambient + diffuse, 1.0);
    vec4 materialColor = vec4(1.0, 0.0, 0.0, 1.0);

    gl_FragColor = finalLightStrength * materialColor;
}

我增加了光线方向uniform vec3 lightDirection;,法线变换矩阵uniform mat4 normalMatrix;

法线不能直接使用modelMatrix进行变换,需要使用modelMatrix的逆转置矩阵,参考维基百科

因为光线是照射到平面的方向,而法线是从平面往外的方向,所以他们相乘之前需要把光照方向反过来,并且要规范化。

vec3 normalizedLightDirection = normalize(-lightDirection);

接着我们将法线变换后再规范化,我们就得到了关键的两个向量。下面是示意图。


将它们相乘最后得到diffuse,可以称它为漫反射强度。漫反射就是投射在粗糙表面上的光向各个方向反射的现象。我们求解diffuse就是模拟的漫反射现象。
代码最后还有一个vec3 ambient = vec3(0.3);是什么呢?根据漫反射的公式,总会有强度为0的地方,为了使场景不那么暗,就增加了一个基本光照强度,也可称为环境光强度。
环境光强度加上漫反射强度就是最后的光照强度finalLightStrength了。光照强度乘以材质本身的颜色materialColor得到最终的颜色,这里材质本身的颜色我用的是红色。

看完Shader,我们回到JS代码。首先,我们需要为法线向量属性提供数据。将buffer改为如下形式,每个顶点数据扩展了3个float作为法线向量数据。

function makeBuffer() {
  var triangle = [
      // Z轴上的平面
      -0.5,   0.5,    0.5,  0, 0, 1,
      -0.5,   -0.5,   0.5,  0, 0, 1,
      0.5,    -0.5,   0.5,  0, 0, 1,
      0.5,    -0.5,   0.5,  0, 0, 1,
      0.5,    0.5,    0.5,  0, 0, 1,
      -0.5,   0.5,    0.5,  0, 0, 1,
      -0.5,   0.5,    -0.5, 0, 0, -1,
      -0.5,   -0.5,   -0.5, 0, 0, -1,
      0.5,    -0.5,   -0.5, 0, 0, -1,
      0.5,    -0.5,   -0.5, 0, 0, -1,
      0.5,    0.5,    -0.5, 0, 0, -1,
      -0.5,   0.5,    -0.5, 0, 0, -1,
      // X轴上的平面
      0.5,    -0.5,   0.5,  1, 0, 0,
      0.5,    -0.5,   -0.5, 1, 0, 0,
      0.5,    0.5,    -0.5, 1, 0, 0,
      0.5,    0.5,    -0.5, 1, 0, 0,
      0.5,    0.5,    0.5,  1, 0, 0,
      0.5,    -0.5,   0.5,  1, 0, 0,
      -0.5,   -0.5,   0.5,  -1, 0, 0,
      -0.5,   -0.5,   -0.5, -1, 0, 0,
      -0.5,   0.5,    -0.5, -1, 0, 0,
      -0.5,   0.5,    -0.5, -1, 0, 0,
      -0.5,   0.5,    0.5,  -1, 0, 0,
      -0.5,   -0.5,   0.5,  -1, 0, 0,
      // Y轴上的平面
      -0.5,   0.5,    0.5,  0, 1, 0,
      -0.5,   0.5,    -0.5, 0, 1, 0, 
      0.5,    0.5,    -0.5, 0, 1, 0,
      0.5,    0.5,    -0.5, 0, 1, 0,
      0.5,    0.5,    0.5,  0, 1, 0,
      -0.5,   0.5,    0.5,  0, 1, 0,
      -0.5,   -0.5,   0.5,  0, -1, 0,
      -0.5,   -0.5,   -0.5, 0, -1, 0,
      0.5,    -0.5,   -0.5, 0, -1, 0,
      0.5,    -0.5,   -0.5, 0, -1, 0,
      0.5,    -0.5,   0.5,  0, -1, 0,
      -0.5,   -0.5,   0.5,  0, -1, 0,
  ];
  buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(triangle), gl.STATIC_DRAW);
  return buffer;
}

以X轴上的两个平面为例,X轴0.5处的平面法线方向是X轴正向,X轴-0.5处的平面法线方向是X轴反向。这样我们才能让朝外的面接收到光线。

接着在渲染时绑定法线向量数据到Shader中的属性。

normalLoc = gl.getAttribLocation(program, 'normal');
gl.enableVertexAttribArray(normalLoc);
gl.vertexAttribPointer(normalLoc, 3, gl.FLOAT, false, 4 * 6, 4 * 3);

4 * 6代表一个顶点数据的字节数,目前是6个float,4 * 3代表法线向量数据从每个顶点数据的4 * 3字节偏移量处开始。

下一步准备一个三维向量存放光照的方向。

var lightDirection = null;

并给它赋值。让它向下照射,所以向量为-Y轴(0,-1,0)。

lightDirection = vec3.fromValues(0, -1, 0);

最后给uniform光照方向和法线变换矩阵赋值。

// 设置光照方向
var lightDirectionUniformLocation = gl.getUniformLocation(program, "lightDirection");
gl.uniform3fv(lightDirectionUniformLocation, lightDirection);


var modelMatrixUniformLoc = gl.getUniformLocation(program, 'modelMatrix');
gl.uniformMatrix4fv(modelMatrixUniformLoc, false, modelMatrix);

var normalMatrix = mat4.create();
mat4.invert(normalMatrix, modelMatrix);
mat4.transpose(normalMatrix, normalMatrix);
var modelMatrixUniformLocation = gl.getUniformLocation(program, "normalMatrix");
gl.uniformMatrix4fv(modelMatrixUniformLocation, false, normalMatrix);

这里我们使用mat4.invertmat4.transpose计算modelMatrix的逆转置矩阵,然后传递给Shader。传递光照方向时使用gl.uniform3fv来传递三维数组。

到此,基本的平行光光照模型就完成了。

下一篇是基础篇的最后一篇,介绍纹理的加载和使用。后续会在进阶篇中介绍高级光照,3D模型加载等更深入的知识。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容