【笔记】《WebGL编程指南》学习(6)

本文详细讲解了WebGL中的光照原理,包括平行光与漫反射、环境光对物体颜色的影响,以及如何处理点光源和运动物体的光照。通过实例演示了如何在立方体上实现不同光源效果,并介绍了逐顶点和逐片元光照的区别。
摘要由CSDN通过智能技术生成

WebGL编程指南学习(6)

6. 光照

光照使场景变得逼真~

6.1 从物理出发

计算机图形学中着色(shading)的真正含义就是,根据光照条件重建“物体各表面明暗不一的效果”的过程

两个物理上的事情需要考虑:

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

光源

  • 平行光(directional light),或者叫方向光,类似太阳光;
  • 点光源(point light),类似灯泡
  • 环境光(ambient light),其他非直射的光
  • 其他特殊的光源,如聚光灯(spot light)

反射类型

物体反射光的方向、反射光的颜色,取决于两个因素:入射光(入射光的方向和颜色)和物体表面的类型(固有颜色和反射特性)

物体表面反射类型有两种:漫反射环境反射

  • 漫反射。针对平行光或点光源。物体表面材质粗糙,反射光以任意角度射出。反射光的颜色取决于入射光的颜色、表面的基底色、入射光与表面形成的入射角

< 漫 反 射 光 颜 色 > = < 入 射 光 颜 色 > × < 表 面 基 底 色 > × cos ⁡ θ <漫反射光颜色>=<入射光颜色>\times<表面基底色>\times \cos\theta <>=<>×<>×cosθ

  • 环境反射。针对环境光。反射光的方向可以认为是入射光的反方向。由于环境光照射物体的方式是各向同的,所以反射光也是各向同的

< 环 境 反 射 光 颜 色 > = < 入 射 光 颜 色 > × < 表 面 基 底 色 > <环境反射光颜色>=<入射光颜色>\times<表面基底色> <>=<>×<>

两种反射同时存在时,将两者加起来,得到最终被观察到的颜色。

注意:不一定要按照上述公式计算!可以随便改!

6.2 平行光下的漫反射

  • 入射光的颜色:RGB值表示
  • 表面的基底色:“物体本来的颜色”,其实就是“物体在标准白光下的颜色”
  • 入射角

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

cos ⁡ θ = < 光 线 方 向 > ⋅ < 法 线 方 向 > \cos\theta = <光线方向>\cdot<法线方向> cosθ=<线><线>

  • 光线方向矢量和表面法线矢量的长度必须为1,否则会导致反射光的颜色过暗或过亮
  • 这里算出的光线方向,实际上是入射方向的反方向,即从入射点指向光源方向
  • 这里用到了表面的法线方向,但是如何获取表面的法线方向?

法线:表面的朝向

一个表面具有两个法向量,“正面”的和“背面”的

平面的法向量唯一

在这里插入图片描述

如图所示,每个顶点对应3个法向量,就像之前每个顶点对应3个颜色值一样。

立方体比较特殊,各表面垂直相交,所以每个顶点对应3个法向量(同时在缓冲区被拆成3个顶点);但是,一些表面光滑的物体,通常其每个顶点只对应1个法向量

例程:发光的Cube

番外:绘制一个立方体

对一个立方体来说,一共8个顶点,但是使用gl.TRIANGLES的话,需要画12个三角形,也就是如要定义36个顶点;使用gl.TRIANGLE_FAN的话,需要画6个面,每个面4个顶点,一共要定义24个顶点

WebGL提供了一种方法:gl.drawElement()替代gl.drawArrays(),能够避免重复定义顶点,保持顶点数量最小,具体方法如下:

  • 立方体一共8个顶点,用一个顶点列表表示。每个顶点有一个索引值,0~7
  • 每3个索引值表示一个三角形,每个表面由两个三角形组成。这样的话,只需要调用不同的索引值即可
gl.drawElements(mode, count, type, offset){
  // mode: 指定绘制的方式,可以接受gl.Point, gl.LINES, gl.LINE_STRIP, gl.LINE_LOOP, gl.TRIANGLES, gl.TRIANGLE_STRIP, 或gl.TRIANGLE_FAN
  // count: 指定绘制顶点的个数(整型数)
  // type: 指定索引值数据类型, gl.UNSIGNED_BYTE 或 gl.UNSIGNED_SHORT
  // offset: 指定索引数组中开始绘制的位置,以字节为单位
}

改变:顶点索引缓冲区gl.ELEMENT_ARRAY_BUFFER

  • 主函数里改变绘制方法
...
	gl.drawElements(gl.TRIANGLES, n, gl.UNSINGED_BYTE, 0);
  • 创建indexBuffer,绑定indexBuffer并向gl.ELEMENT_ARRAY_BUFFER写入数据
...
    var indices = new Uint8Array([
        0, 1, 2,    0, 2, 3,    // front
        0, 3, 4,    0, 4, 5,    // right
        0, 5, 6,    0, 6, 1,    // up
        1, 6, 7,    1, 7, 2,    // left
        7, 4, 3,    7, 3, 2,    // down
        4, 7, 6,    4, 6, 5,    // back
    ]);
...
    var indexBuffer = gl.createBuffer();
...
    // 把索引写入缓冲对象
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
...
		return indices.length; // drawElement需要知道索引数组的长度

注意gl.drawElement()第二个参数n表示顶点索引数组的长度,也就是顶点着色器的执行次数,和gl.ARRAY_BUFFER中的顶点个数不同

通过索引方式访问顶点数据可以控制内存的开销;代价是需要通过索引间接地访问顶点,在某种程度上是程序复杂化了

新的问题:能否让立方体的每个面显示相同的颜色?

  • 把归属不同面的顶点拆分开,或者说,创建多个具有相同坐标的顶点,不同的拷贝对应不同的颜色
  • 依然采用索引的方式,只更新了顶点数组和索引数组

在这里插入图片描述

正传:加上方向光
  • 在上面代码的基础上,修改顶点着色器,利用前述方程计算逐点的颜色;同时增加了法向量属性;
  • 对应在外部,增加法向量数组,以及传入着色器的相关代码
// 顶点着色器
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(a_Normal.xyz);\n' +
    '   float nDotL = max(dot(u_LightDirection, normal), 0.0);\n' +
    '   vec3 diffuse = u_LightColor * a_Color.rgb * nDotL;\n' +
    '   v_Color = vec4(diffuse, a_Color.a);\n' +
    '}\n';
// 主函数里为着色器传递新增的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');
...

在这里插入图片描述

6.3 环境光下的漫反射

  • 环境光反射产生的颜色只取决于光的颜色和表面基底色
  • 最终表面反射光颜色为漫反射光颜色+环境反射光颜色

小修

  • 着色器里加上环境光颜色属性,修改反射光计算公式
  • JavaScript处理环境光的传入

在这里插入图片描述

6.4 运动物体的光照

物体旋转时,表面的法向量可能会变化

  • 平移变换不会改变法向量
  • 旋转变换会改变法向量
  • 缩放变换对法向量的影响较为复杂:如果缩放比例在所有轴上都一致的话,法向量不会变(各向同);即使不一致,也不一定会变化(大多数情况会变化)

怎么求旋转后物体表面法向量?

变换之前的法向量乘以模型矩阵的逆转置矩阵(inverse transpose matrix)

6.5 点光源

点光源发射的光线,在不同位置上方向不同。因此,需要在每个入射点计算点光源在该处的方向

点光源的方向不再是常量,而要根据每个顶点的位置逐一计算,着色器需要知道点光源光自身的所在位置,而不是光的方向

  • 顶点着色器:需要计算光线方向

计算光线在顶点处的方向

  1. 首先使用模型矩阵变换顶点坐标,得到顶点在世界坐标系中的坐标,以便计算点光源在顶点处的方向
  2. 顶点处的光线方向是由点光源坐标减去顶点坐标得到的矢量,然后归一化
  3. 然后计算光线方向矢量与顶点表面法向量的点积,最终算出每个顶点的颜色
// 修改着色器
...
uniform mat4 u_ModelMatrix; // 模型矩阵
uniform vec3 u_LightPosition; // 光源位置
...
  // 计算顶点的世界坐标
	vec4 vextexPosition = u_ModelMatrix * a_Position;
	// 计算光线方向并归一化
	vec3 lightDirection = normalize(u_LightPosition - vec3(vertexPosition));
...

这是逐顶点的点光源光照模型Blinn-Phone光照模型

点光源照射到表面,产生的效果(每个片元获得的颜色)与简单使用4个顶点颜色内插出的效果并不完全相同

在这里插入图片描述

逐顶点的渲染效果

更逼真:逐片元光照

把顶点着色器中计算逐点光源位置还有方向的事儿交给片元着色器

  • 利用varying变量,把顶点位置还有法向变成varying变量交给片段着色器
  • 计算光源位置和方向放到片元着色器

根据varying变量的特性,每处理一个片元,就会计算一次光照

在这里插入图片描述

逐片元的渲染效果

基本信息 原书名:WebGL Programming Guide: Interactive 3D Graphics Programming with WebGL (OpenGL) 原出版社: Addison-Wesley Professional 作者: ()Kouichi Matsuda Rodger Lea(松田浩一,罗杰.李) 译者: 谢光磊 出版社:电子工业出版社 ISBN:9787121229428 上架时间:2014-6-11 出版日期:2014 年6月 开本:16开 页码:470 版次:1-1 ---------------------------------------- 目录 《WebGL编程指南》 第1 章 WebGL 概述 1 WebGL 的优势 3 使用文本编辑器开发三维应用 3 轻松发布三维图形程序 4 充分利用浏览器的功能 5 学习和使用WebGL 很简单 5 WebGL 的起源 5 WebGL 程序的结构 6 总结 7 第2 章 WebGL 入门 9 Canvas 是什么? 10 使用[canvas] 标签 11 DrawRectangle.js 13 最短的WebGL 程序:清空绘图区 16 HTML 文件(HelloCanvas.html) 16 JavaScript 程序(HelloCanvas.js) 17 用示例程序做实验 22 绘制一个点(版本1) 22 HelloPoint1.html 24 HelloPoint1.js 24 着色器是什么? 25 使用着色器的WebGL 程序的结构 27 初始化着色器 29 顶点着色器 31 片元着色器 33 绘制操作 34 WebGL 坐标系统 35 用示例程序做实验 37 绘制一个点(版本2) 38 使用attribute 变量 38 示例程序(HelloPoint2.js) 39 获取attribute 变量的存储位置 41 向attribute 变量赋值 42 gl.vertexAttrib3f() 的同族函数 44 用示例程序做实验 45 通过鼠标点击绘点 46 示例程序(ClickedPoints.js) 47 注册事件响应函数 48 响应鼠标点击事件 50 用示例程序做实验 53 改变点的颜色 55 示例程序(ColoredPoints.js) 56 uniform 变量 58 获取uniform 变量的存储地址 59 向uniform 变量赋值 60 gl.uniform4f() 的同族函数 61 总结 62 第3 章 绘制和变换三角形 63 绘制多个点 64 示例程序(MultiPoint.js) 66 使用缓冲区对象 69 创建缓冲区对象(gl.createBuffer()) 70 绑定缓冲区(gl.bindBuffer()) 71 向缓冲区对象中写入数据(gl.bufferData()) 72 类型化数组 74 将缓冲区对象分配给attribute 变量(gl.vertexAttribPointer()) 75 开启attribute 变量(gl.enableVertexAttribArray()) 77 gl.drawArrays() 的第2 个和第3 个参数 78 用示例程序做实验 79 Hello Triangle 80 示例程序(HelloTriangle.js) 80 基本图形 82 用示例程序做实验 83 Hello Rectangle(HelloQuad) 84 用示例程序做实验 85 移动、旋转和缩放 86 平移 87 示例程序(TranslatedTriangle.js) 88 旋转 91 示例程序(RotatedTriangle.js) 93 变换矩阵:旋转 97 变换矩阵:平移 100 4×4 的旋转矩阵 101 示例程序(RotatedTriangle_Matrix.js) 102 平移:相同的策略 105 变换矩阵:缩放 106 总结 108 第4 章 高级变换与动画基础 109 平移,然后旋转 109 矩阵变换库:cuon-matrix.js 110 示例程序(RotatedTriangle_Matrix4.js) 111 复合变换 113 示例程序(RotatedTranslatedTriangle.js) 115 用示例程序做实验 117 动画 118 动画基础 119 示例程序(RotatingTriangle.js) 119 反复调用绘制函数(tick()) 123 按照指定的旋转角度绘制三角形(dr
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值