案例查看地址:点击这里

通过上一节的介绍,我们可以知道,平行光下的漫反射光的颜色可以由一下式子得出:

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

所以,我们要计算出来漫反射的光的颜色需要三项数据:

(1)平行入射光的颜色

(2)表面的基底色

(3)入射光与表面形成的入射角  θ

但是,我们没有办法直接就确定 θ值是多少,我们必须根据入射光的方向和物体表面的朝向(即法线方向)来计算出入射角。这需要我们先确定每个表面的朝向,在指定光源的时候,再确定光的方向,来计算出入射角,并确定 θ的值。

幸运的是,我们可以通过计算两个矢量的点积,来计算着两个矢量的夹角余弦值cos  θ。点积运算的使用非常频繁,GLSL ES内置了点积运算函数(详见GLSL ES中的第11节,11 WebGL 着色器编程语言GLSL ES的内置函数)。在公式中,我们使用点符号 · 来表示点积运算。这样,cos θ就可以通过下式计算出来:

cos θ = <光线方向> · <法线方向>

所以,上面计算平行光下的漫反射的颜色的式子就可以修改成:

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

这里有两点需要注意:

(1)光线方向矢量和表面法线矢量的长度必须为1,否则反射光的颜色就会过暗或过亮。将一个矢量的长度调整为1,同时保持方向不变的过程称之为归一化。GLSL ES提供了内置的归一化函数,你可以直接使用。

(2)这里所谓的“光线方向”,实际上是入射方向的反方向,即从入射点指向光源方向(因为这样,该方向与法线方向的夹角才是入射角),如图。

法线:表面的朝向

物体表面的朝向,即垂直于表面的方向,又称法线或法向量。法向量有三个分量,向量(nx,ny,nz)表示从原点(0,0,0)指向点(nx,ny,nz)的方向。比如说,向量(1,0,0)表示x轴正方向,向量(0,0,1)表示z轴正方向。涉及到表面和法向量的问题时,必须考虑以下两点:

(1)一个表面具有两个法向量

每个表面都有两个面,“正面”和“背面”。两个面各自具有一个法向量。比如,垂直于z轴的x-y平面,其正面的法向量为z正半轴,即(0,0,1),背面的法向量为z负半轴,即(0,0,-1)。

在三维图形学中,表面的正面和背面取决于绘制表面时的顶点顺序。当你按照v0,v1,v2,v3的顶点顺序绘制了一个平面,那么当你从正面观察这个表面时,这4个顶点是顺时针的,而你从背面观察该表面,这四个顶点就是逆时针的。

(2)平面的法向量唯一

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

进一步说,即使有两个不同的平面,只要朝向相同(也就是两个平面平行),法向量也相同。

一旦计算好每个平面的法向量,接下来的任务就是将数据传给着色器程序。以前的程序把颜色作为“逐顶点数据”存储在缓冲区中,并传给着色器。对法向量数据也可以这样做。上图所示,每个顶点对应3个法向量,就像之前每个顶点都对应3个颜色值一样。我们明白了这一点以后,就可以通过法向量获取平行光照后的颜色值了。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Title</title><style>body {margin: 0;text-align: center;}#canvas {margin: 0;}</style>
</head>
<body οnlοad="main()">
<canvas id="canvas" height="800" width="1200"></canvas>
</body>
<script src="lib/webgl-utils.js"></script>
<script src="lib/webgl-debug.js"></script>
<script src="lib/cuon-utils.js"></script>
<script src="lib/cuon-matrix.js"></script>
<script>//顶点着色器var VSHADER_SOURCE = "" +"attribute vec4 a_Position;\n" +"attribute vec4 a_Color;\n" +"attribute vec4 a_Normal;\n" +//法向量"uniform mat4 u_MvpMatrix;\n" +"uniform vec3 u_LightColor;\n" +//光线颜色"uniform vec3 u_LightDirection;\n" +//入射光方向,归一化的世界坐标"varying vec4 v_Color;\n" +"void main(){\n" +"   gl_Position = u_MvpMatrix * a_Position;\n" +//对法向量进行归一化"   vec3 normal = normalize(vec3(a_Normal));\n" +//计算光线方向和法向量的点积"   float nDotL = max(dot(u_LightDirection,normal),0.0);\n" +//计算漫反射光的颜色"   vec3 diffuse = u_LightColor * vec3(a_Color) * nDotL;\n" +"   v_Color = vec4(diffuse,a_Color.a);\n" +"}";//片元着色器var FSHADER_SOURCE = "" +"#ifdef GL_ES\n" +"precision mediump float;\n" +"#endif\n" +"varying vec4 v_Color;\n" +"void main(){\n" +"   gl_FragColor = v_Color;\n" +"}";//主函数,页面加载完成触发function main() {//获取canvas对象var canvas = document.getElementById("canvas");//获取WebGL上下文var gl = getWebGLContext(canvas);if (!gl) {console("您的浏览器不支持WebGL");return;}//初始化着色器if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {console.log("初始化着色器失败");return;}//设置顶点的坐标、颜色和法向量var n = initVertexBuffers(gl);if (n < 0) {console.log("无法获取到顶点个数,设置顶点坐标、颜色和法向量失败");return;}//初始化背景色和前后关系功能开启gl.clearColor(0, 0, 0, 1);gl.enable(gl.DEPTH_TEST);//获取模型视图投影矩阵、光线颜色变量和归一化世界坐标uniform变量的存储位置var u_MvpMatrix = gl.getUniformLocation(gl.program, "u_MvpMatrix");var u_LightColor = gl.getUniformLocation(gl.program, "u_LightColor");var u_LightDirection = gl.getUniformLocation(gl.program, "u_LightDirection");if (!u_MvpMatrix || !u_LightColor || !u_LightDirection) {console.log("无法获取相关的存储位置,或者未定义");return;}//设置光线颜色(白色)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);//计算模型视图投影矩阵var mvpMatrix = new Matrix4(); //声明一个矩阵模型mvpMatrix.setPerspective(30, canvas.width / canvas.height, 1, 100);//设置透视矩阵mvpMatrix.lookAt(3, 3, 7, 0, 0, 0, 0, 1, 0);//将模型视图投影矩阵传给u_MvpMatrix变量gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);//清除底色和深度缓冲区gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0); //绘制图形}function initVertexBuffers(gl) {// 绘制一个立方体//    v6----- v5//   /|      /|//  v1------v0|//  | |     | |//  | |v7---|-|v4//  |/      |///  v2------v3var vertices = new Float32Array([   // 顶点坐标1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, // v0-v1-v2-v3 front1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, // v0-v3-v4-v5 right1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, // v0-v5-v6-v1 up-1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, // v1-v6-v7-v2 left-1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, // v7-v4-v3-v2 down1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0  // v4-v7-v6-v5 back]);var colors = new Float32Array([    // 顶点颜色1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,     // v0-v1-v2-v3 front1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,     // v0-v3-v4-v5 right1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,     // v0-v5-v6-v1 up1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,     // v1-v6-v7-v2 left1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,     // v7-v4-v3-v2 down1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0     // v4-v7-v6-v5 back]);var normals = new Float32Array([    // 法向量0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0,  // v0-v1-v2-v3 front1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0,  // v0-v3-v4-v5 right0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,  // v0-v5-v6-v1 up-1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0,  // v1-v6-v7-v2 left0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0,  // v7-v4-v3-v2 down0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0   // v4-v7-v6-v5 back]);// 绘制点的顺序下标var indices = new Uint8Array([0, 1, 2, 0, 2, 3,    // front4, 5, 6, 4, 6, 7,    // right8, 9, 10, 8, 10, 11,    // up12, 13, 14, 12, 14, 15,    // left16, 17, 18, 16, 18, 19,    // down20, 21, 22, 20, 22, 23     // back]);// 通过initArrayBuffer方法将顶点数据保存到缓冲区if (!initArrayBuffer(gl, 'a_Position', vertices, 3, gl.FLOAT)) return -1;if (!initArrayBuffer(gl, 'a_Color', colors, 3, gl.FLOAT)) return -1;if (!initArrayBuffer(gl, 'a_Normal', normals, 3, gl.FLOAT)) return -1;// 创建顶点索引缓冲区对象var indexBuffer = gl.createBuffer();if (!indexBuffer) {console.log('无法创建顶点索引的缓冲区对象');return -1;}//将顶点索引数据存入缓冲区gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);return indices.length;}function initArrayBuffer(gl, attribute, data, num, type) {//创建缓冲区对象var buffer = gl.createBuffer();if (!buffer) {console.log("无法创建缓冲区对象");return false;}//绑定缓冲区,并将数据存入缓冲区gl.bindBuffer(gl.ARRAY_BUFFER, buffer);gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);//获取相关变量存储位置,赋值并开启缓冲区var a_attribute = gl.getAttribLocation(gl.program, attribute);if (a_attribute < 0) {console.log("无法获取" + attribute + "变量的相关位置");return false;}//向缓冲区赋值gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0)//开启数据,并解绑缓冲区gl.enableVertexAttribArray(a_attribute);gl.bindBuffer(gl.ARRAY_BUFFER, null);return true;}
</script>
</html>

在顶点着色器中,我们实现了漫反射光颜色的计算,使用的是:

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

计算漫反射光颜色需要:(1)入射光颜色 (2)表面基底色 (3)入射光方向 (4)表面法线方向。其中后两者都必须是归一化的(即长度为1.0)。

在上面的案例代码中:

a_Color 变量表示表面基底色

a_Normal变量表示表面法线方向

u_LightColor变量表示入射光颜色

u_LightDirection变量表示入射光方向(是在世界坐标系下的,在javascript中归一化了,可以避免着色器每次执行都对它进行归一化)

相关的变量已经获取,下面是进行处理:

着色器部分处理:

首先对a_Normal法线变量进行归一化,虽然写的值是已经归一化过的,但是顶点着色器可不知道传入的矢量是否经过了归一化,而且这里没有节省开销的理由(法向量是逐顶点的),由于使用x,y和z三个分量就可以表示法线方向,我们需要将这三个分量提取出来,使用到了vec3()方法,然后使用内置函数normalize()函数进行归一化。

接下来,我们计算点积<光线方向> · <法线方向>。法线方向已经被归一化,光线方向在传入进来之前已经被归一化。直接使用GLSL ES提供的内置函数dot()进行点积运算。然后又使用max()函数时因为如果计算出来的数大于90度的话,就已经在背面了。我们这是计算的不透明的,所以在背面就是0。

计算出来点积后,就可以继续套用公式进行漫反射光的颜色,计算出漫反射光的rgb颜色。

最后再把rgba的a给补上,最后就把颜色给计算了出来,再把颜色通过varying变量v_Color传到片元着色器。

js部分处理:

首先就给u_LightColor赋值了光线的颜色。

然后给u_LightDirection赋值光线方向,再使用cuon-matrix.js里面封装好的函数,直接在对象上调用一下,使对象的值归一化,最后把值赋值给了u_LightDirection。

最后在颜色下面声明了一个存储法向量的Float32Array将值存储,和顶点和颜色一起赋值。

31 WebGL平行光下的漫反射光的计算相关推荐

  1. 31 | GPU(下):为什么深度学习需要使用 GPU?

    31 | GPU(下):为什么深度学习需要使用 GPU? 上一讲,我带你一起看了三维图形在计算机里的渲染过程.这个渲染过程,分成了顶点处理.图元处理. 栅格化.片段处理,以及最后的像素操作.这一连串的 ...

  2. python计算存款复利计算器_分享下自己写的计算余额宝上复利的代码,网上的复利计算器无法满足需求...

    分享下自己写的计算余额宝上复利的代码,网上的复利计算器无法满足需求 每年存10万10%的回报连续存20年后复利=6400249.9 (钱足够多的话10%的回报还是可以找到的) 每月存1万,10%的年回 ...

  3. 走过19年,每年千万下载量,科学计算开源库SciPy的前世今生

    点击上方"AI遇见机器学习",选择"星标"公众号 重磅干货,第一时间送达 转自:机器之心 每年千万下载量,科学计算开源库 SciPy,你已经是个成熟的小伙伴了. ...

  4. 在置信区间下置信值的计算_使用自举计算置信区间

    在置信区间下置信值的计算 嗨,大家好, (Hi everyone,) In this article, I will attempt to explain how we can find a conf ...

  5. 在EXCEL表格中经常会遇到有合并单元格时,汇总计算的公式无法直接下拉自动填充计算,掌握这个小技巧一键汇总

    在EXCEL表格中经常会遇到有合并单元格时,汇总计算的公式无法直接下拉自动填充计算,掌握这个小技巧一键汇总 目录 在EXCEL表格中经常会遇到有合并单元格时,汇总计算的公式无法直接下拉自动填充计算,掌 ...

  6. 【20保研】南京邮电大学关于举办2019 年江苏省研究生“大数据时代下的新型网络计算” 暑期学校的通知...

    点击文末的阅读原文或者公众号界面左下角的保研夏令营或者公众号回复"夏令营"是计算机/软件等专业的所有保研夏令营信息集合,会一直更新的. 主办单位:江苏省工学2类研究生教育指导委员会 ...

  7. 基于C/C++在Debug模式与Release模式下相同的算法计算出来的结果不一样

    项目场景: TEA算法踩坑记,在Debug模式与Release模式下相同的算法计算出来的结果不一样. 在安全学领域,TEA(Tiny Encryption Algorithm)是一种分组加密算法,它的 ...

  8. c语言windows获取时间,【转载】c/c++在windows下获取时间和计算时间差的几种方法总结...

    一.标准C和C++都可用 1.获取时间用time_t time( time_t * timer ),计算时间差使用double difftime( time_t timer1, time_t time ...

  9. WebGL 平行光和环境光

    冯氏反射模型引申了这个四步走的光照系统,首先所有的光线都有以下两个属性: 发生漫反射光的RGB值. 发生镜面反射光的RGB值. 其次所有材质都有以下四个属性 反射的环境光RGB值 反射的漫反射光RGB ...

最新文章

  1. Android之单复选框及Spinner实现二级联动
  2. 通通玩blend美工(6)下——仿iPhone滚动选择器的ListBox(交互逻辑)
  3. mysql cst_一次 JDBC 与 MySQL 因 “CST” 时区协商误解导致时间差了 14 或 13 小时的排错经历...
  4. springmvc默认方法名为映射名_SpringIoC和SpringMVC的快速入门
  5. Summernote个性化定制使用帮助(二)
  6. Ruby——关于require与require_relative
  7. jave double相加结果误差+尾巴
  8. 软件测试仓库管理信息系统,仓库管理系统测试报告
  9. Nero Burning Rom V6多区段刻录详解
  10. 社团挖掘算法——BGLL算法
  11. 【论文译文】BigGAN
  12. HTML跳转php没反应的问题解决
  13. python存钱挑战_案例(4):52周存钱法
  14. CSS3 Shape详解
  15. C语言发展史——程序猿抗争史
  16. laravel--6 eloquent查询作用域
  17. iVMS-4200 Vs区别_异地恋 VS 网恋,哪个更难坚持到最后?| Newth互动158
  18. 如何利用在线工具更改寸照底色
  19. 计算机的硬盘维修,计算机硬盘的维修方法和技巧
  20. 拿什么拯救你,我的理财收益率

热门文章

  1. 手机有线网络测试软件,iOS-用手机网络测试Ipv6
  2. Nowcoder专项练习:C++(七)
  3. 软件测试相关应届生,毕业就失业?这该怎么办....
  4. 不用手机电脑也可以聊微信:DNweixin微信电脑版
  5. css各种形状怎么画,CSS3 画形状
  6. 《人人都是产品经理V2.0》读书笔记——第02章
  7. 人人都是产品经理系列笔记1
  8. java窗口how2j_http://how2j.cn/frontshowJavaPractises
  9. Kubebuilder Hello World
  10. CephFS文件存储系统