Webgl 难记词汇及笔记[二]

顶点连接顺序(Winding order)
默认情况下,逆时针的顶点连接顺序被定义为三角形的正面。
当定义你的顶点顺序时,你如果定义能够看到的一个三角形,那它一定是正面朝向的,所以你定义的三角形应该是逆时针的,就像你直接面向这个三角形。把所有的顶点指定成这样是件炫酷的事,实际的顶点连接顺序是在光栅化阶段(Rasterization stage)计算的,所以当顶点着色器已经运行后。顶点就能够在观察者的观察点被看到。
参考引用:https://www.jianshu.com/p/ee04165f2a02

面剔除(Face culling)gl.CULL_FACE
顶点连接顺序(Winding order)
面剔除(Face culling)所要做的是允许检查所有正面朝向(Front facing)观察者的面,并渲染它们,而丢弃所有背面朝向(Back facing)的面
参考引用:https://www.jianshu.com/p/ee04165f2a02

RLE 全称(run-length encoding)
翻译为游程编码,又译行程长度编码,又称变动长度编码法(run coding)
如下面:把重复的顶点使用 RLE 编码压缩,使用时,再调用 expandRLEData 解压出来

  const normals = expandRLEData([
    // left column front
    // top rung front
    // middle rung front
    18, 0, 0, 1,

    // left column back
    // top rung back
    // middle rung back
    18, 0, 0, -1,

    // top
    6, 0, 1, 0,

    // top rung right
    6, 1, 0, 0,

    // under top rung
    6, 0, -1, 0,

    // between top rung and middle
    6, 1, 0, 0,

    // top of middle rung
    6, 0, 1, 0,

    // right of middle rung
    6, 1, 0, 0,

    // bottom of middle rung.
    6, 0, -1, 0,

    // right of bottom
    6, 1, 0, 0,

    // bottom
    6, 0, -1, 0,

    // left side
    6, -1, 0, 0,
  ]);
/**
 * Expands RLE data
 * @param {number[]} rleData data in format of run-length, x, y, z, run-length, x, y, z
 * @param {number[]} [padding] value to add each entry with.
 * @return {number[]} the expanded rleData
 */
function expandRLEData (rleData, padding) {
  padding = padding || [];
  const data = [];
  for (let ii = 0; ii < rleData.length; ii += 4) {
    const runLength = rleData[ii];
    const element = rleData.slice(ii + 1, ii + 4);
    element.push.apply(element, padding);
    for (let jj = 0; jj < runLength; ++jj) {
      data.push.apply(data, element);
    }
  }
  return data;
}

参考引用:https://webglfundamentals.org/webgl/resources/primitives.js

gl.DEPTH_TESTgl.BLEND
gl.enable(gl.DEPTH_TEST) 启用了之后,OpenGL 在绘制的时候就会检查,当前像素前面是否有别的像素,如果别的像素挡道了它,那它就不会绘制,也就是说,OpenGL 就只绘制最前面的一层。

当我们需要绘制透明图片时,就需要关闭它
gl.disable(gl.DEPTH_TEST);

并且打开混合
gl.enable(gl.BLEND)

基于源像素 Alpha 通道值的半透明混合函数
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)

gl.depthMask(false) 锁定深度缓冲区的写入操作,使之只读 (深度缓冲区用于隐藏面消除)
gl.depthMask(mask)
锁定或释放深度缓冲区的写入操作
mask: 锁定深度缓冲区的写入操作 false,释放 true

参考引用:https://www.cnblogs.com/edwardloveyou/p/10801422.html

标准长方体顶点索引 CUBE_FACE_INDICES

const CUBE_FACE_INDICES = [
  [3, 7, 5, 1], // right
  [6, 2, 0, 4], // left
  [6, 7, 3, 2], // top
  [0, 1, 5, 4], // bottom
  [7, 6, 4, 5], // front
  [2, 3, 1, 0], // back
];

图示如下:
Array of the indices of corners of each face of a cube
参考引用:https://webglfundamentals.org/webgl/resources/primitives.js

UVs vs. 纹理坐标
纹理坐标经常被简写为 texture coords,texcoords 或 UVs(发音为 Ew-Vees)
顶点位置使用 x, y, z, w, 所以对于纹理坐标有人决定使用s, t, u, v,好让清楚使用的两个类型的区别。 有了这些可能应该读作 Es-Tees,因为纹理包裹的设置被叫做 TEXTURE_WRAP_S 和 TEXTURE_WRAP_T, 但是出于某些原因,有人叫它 Ew-Vees

如下图要分别贴到一个正方体上,如何求纹理坐标?
如何求纹理坐标
注意,一个面是由两个三角形组成,因此画一个面需要 3 * 2 = 6 个点,对应纹理坐标(x, y)需要 6 个。把图归一化后,按逆时取点。

// Fill the buffer with texture coordinates the cube.
function setTexcoords(gl) {
  gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array(
        [
          // 选择左下图
          0   , 0  ,
          0   , 0.5,
          0.25, 0  ,
          0   , 0.5,
          0.25, 0.5,
          0.25, 0  ,
          // 选择中下图
          0.25, 0  ,
          0.5 , 0  ,
          0.25, 0.5,
          0.25, 0.5,
          0.5 , 0  ,
          0.5 , 0.5,
          // 选择中右图
          0.5 , 0  ,
          0.5 , 0.5,
          0.75, 0  ,
          0.5 , 0.5,
          0.75, 0.5,
          0.75, 0  ,
          // 选择左上图
          0   , 0.5,
          0.25, 0.5,
          0   , 1  ,
          0   , 1  ,
          0.25, 0.5,
          0.25, 1  ,
          // 选择中上图
          0.25, 0.5,
          0.25, 1  ,
          0.5 , 0.5,
          0.25, 1  ,
          0.5 , 1  ,
          0.5 , 0.5,
          // 选择右上图
          0.5 , 0.5,
          0.75, 0.5,
          0.5 , 1  ,
          0.5 , 1  ,
          0.75, 0.5,
          0.75, 1  ,
      ]),
      gl.STATIC_DRAW);
}

参考引用:https://webglfundamentals.org/webgl/lessons/zh_cn/webgl-3d-textures.html

glDepthFunc
函数原型:void glDepthFunc(GLenum func);
函数功能:指定“目标像素与当前像素在z方向上值大小比较”的函数,符合该函数关系的目标像素才进行绘制,否则对目标像素不予绘制。
参数说明:
func 指定深度比较函数,GL_NEVER,GL_LESS,GL_EQUAL,GL_LEQUAL,GL_GREATER,GL_NOTE_QUAL,GL_GEQUAL,GL_ALWAYS, 缺省值GL_LESS,

GL_NEVER,不通过(输入的深度值不取代参考值)

GL_LESS,如果输入的深度值小于参考值,则通过

GL_EQUAL,如果输入的深度值等于参考值,则通过

GL_LEQUAL,如果输入的深度值小于或等于参考值,则通过

GL_GREATER,如果输入的深度值大于参考值,则通过

GL_NOTE_QUAL,如果输入的深度值不等于参考值,则通过

GL_GEQUAL,如果输入的深度值大于或等于参考值,则通过

GL_ALWAYS,总是通过(输入的深度值取代参考值)

描述:

通过目标像素与当前像素在z方向上值大小的比较是否满足参数指定的条件,来决定在深度(z方向)上是否绘制该目标像素。该函数只有启用“深度测试”时才有效,参考 glEnable(GL_DEPTH_TEST) 和 glDisable(GL_DEPTH_TEST)

参考引用:
https://blog.csdn.net/u012463389/article/details/50748128
https://blog.csdn.net/chenyu19880302/article/details/10189373

帧缓冲(framebuffer)
帧缓冲只是一个附件集,附件是纹理或者 renderbuffers, 我们之前讲过纹理,Renderbuffers 和纹理很像但是支持纹理不支持的格式和可选项,同时, 不能像纹理那样直接将 renderbuffer 提供给着色器。
参考引用:
https://webglfundamentals.org/webgl/lessons/zh_cn/webgl-render-to-texture.html

骨架 skeleton 骨骼 bone 蒙皮 skin
把三维骨骼顶点联系至骨骼的位置的过程,称为蒙皮
能把网格顶点从原来位置(绑定姿势)变换至骨骼的当前姿势的矩阵称为蒙皮矩阵
蒙皮矩阵不是基变更变换(change of basis)
基的变更:把物体从一个坐标系转换到另外一个坐标系的过程,明显,蒙皮矩阵并不是基的变更
蒙皮矩阵把顶点变形至新位置,顶点在变换前后都在模型变换空间中
求蒙皮矩阵时的一个诀窍是:顶点绑定到关节的位置时,在该关节空间中是不变的(其实变的只是骨骼,所以才叫骨骼动画)
通俗点一点理解就是:模型加载到内存中的位置是在模型空间中的坐标(并不是其绑定的骨骼坐标系下)在做骨骼动画时,动的其实是骨骼,而绑定到该骨骼的顶点会跟随骨骼做动画,所以顶点相对于骨骼位置是不变的(在关节空间下是不变的,变的只是骨骼的位置),因此可以利用这个特性,求出蒙皮矩阵
参考引用:
https://blog.csdn.net/smsmn/article/details/53870334?utm_source=blogxgwz6
https://www.cnblogs.com/Unknw/p/6689176.html

骨骼索引与骨骼权重
在顶点属性中加入骨骼索引和骨骼权重,用于顶点蒙皮
顶点 shader,支持每个顶点索引四个骨骼
骨骼更新
在每帧渲染之前,先更新所有的骨骼矩阵
更新完骨骼矩阵后传入shader,进行顶点蒙皮
参考引用:
https://blog.csdn.net/crystal_do/article/details/46406239
https://www.cnblogs.com/tandier/p/10087656.html

ArrayBuffer 对象、TypedArray 视图和 DataView 视图是 JavaScript 操作二进制数据的一个接口
可以自定义复合格式的视图,比如第一个字节是 Uint8(无符号 8 位整数)、第二、三个字节是 Int16(16 位整数)、第四个字节开始是 Float32(32 位浮点数)等等,此外还可以自定义字节序。

简单说,ArrayBuffer 对象代表原始的二进制数据,TypedArray 视图用来读写简单类型的二进制数据,DataView 视图用来读写复杂类型的二进制数据。

TypedArray视图支持的数据类型一共有 9 种(DataView视图支持除Uint8C以外的其他 8 种)。

数据类型 字节长度 含义 对应的 C 语言类型
Int8 1 8 位带符号整数 signed char
Uint8 1 8 位不带符号整数 unsigned char
Uint8C 1 8 位不带符号整数(自动过滤溢出) unsigned char
Int16 2 16 位带符号整数 short
Uint16 2 16 位不带符号整数 unsigned short
Int32 4 32 位带符号整数 int
Uint32 4 32 位不带符号的整数 unsigned int
Float32 4 32 位浮点数 float
Float64 8 64 位浮点数 double

注意,二进制数组并不是真正的数组,而是类似数组的对象。
TypedArray 溢出
不同的视图类型,所能容纳的数值范围是确定的。超出这个范围,就会出现溢出。比如,8 位视图只能容纳一个 8 位的二进制值,如果放入一个 9 位的值,就会溢出。

TypedArray 数组的溢出处理规则,简单来说,就是抛弃溢出的位,然后按照视图类型进行解释。

const uint8 = new Uint8Array(1);

uint8[0] = 256;
uint8[0] // 0

uint8[0] = -1;
uint8[0] // 255

上面代码中,uint8是一个 8 位视图,而 256 的二进制形式是一个 9 位的值 100000000,这时就会发生溢出。根据规则,只会保留后 8 位,即00000000。uint8 视图的解释规则是无符号的 8 位整数,所以 00000000 就是 0。

负数在计算机内部采用 “2 的补码” 表示,也就是说,将对应的正数值进行否运算,然后加1。比如,-1 对应的正值是 1,进行否运算以后,得到 11111110,再加上1就是补码形式 11111111。uint8 按照无符号的 8 位整数解释 11111111,返回结果就是 255。

一个简单转换规则,可以这样表示。

正向溢出(overflow):当输入值大于当前数据类型的最大值,结果等于当前数据类型的最小值加上余值,再减去 1。
负向溢出(underflow):当输入值小于当前数据类型的最小值,结果等于当前数据类型的最大值减去余值的绝对值,再加上 1。
上面的 “余值” 就是模运算的结果,即 JavaScript 里面的 % 运算符的结果。

详细参考:https://www.cnblogs.com/jixiaohua/p/10714662.html#_label2

TypedArray 视图
概述
ArrayBuffer 对象作为内存区域,可以存放多种类型的数据。同一段内存,不同数据有不同的解读方式,这就叫做“视图”(view)。

ArrayBuffer有两种视图,一种是 TypedArray 视图,另一种是 DataView 视图。前者的数组成员都是同一个数据类型,后者的数组成员可以是不同的数据类型。

目前,TypedArray视图一共包括 9 种类型,每一种视图都是一种构造函数。

Int8Array:8 位有符号整数,长度 1 个字节。
Uint8Array:8 位无符号整数,长度 1 个字节。
Uint8ClampedArray:8 位无符号整数,长度 1 个字节,溢出处理不同。
Int16Array:16 位有符号整数,长度 2 个字节。
Uint16Array:16 位无符号整数,长度 2 个字节。
Int32Array:32 位有符号整数,长度 4 个字节。
Uint32Array:32 位无符号整数,长度 4 个字节。
Float32Array:32 位浮点数,长度 4 个字节。
Float64Array:64 位浮点数,长度 8 个字节。
这 9 个构造函数生成的数组,统称为 TypedArray 视图。

普通数组的操作方法和属性,对 TypedArray 数组完全适用。
另外常见 API

TypedArray.prototype.set()
TypedArray.prototype.slice()
TypedArray.prototype.subarray()
TypedArray.of()
TypedArray.from()
TypedArray.prototype.byteLength,TypedArray.prototype.byteOffset
TypedArray.prototype.buffer

详细参考:https://www.cnblogs.com/jixiaohua/p/10714662.html#_label2

Skybox 天空盒
天空盒材质是出现在场景中所有物体后面的材质,用于模拟天空或者远处的背景。

更多关于光的光照基础参考:
https://www.cnblogs.com/Mr-QingZi/p/10485941.html
https://blog.csdn.net/GameObject14715/article/details/81703916

20个不可思议的 WebGL 示例和演示

魔方:http://www.randelshofer.ch/webgl/rubikscube/

超级 3D球: https://richardmattka.com/

性能优化相关
https://zhuanlan.zhihu.com/p/154425898
https://www.cnblogs.com/mfrbuaa/p/5244094.html

环境光遮蔽(Ambient Occlusion)
https://zhuanlan.zhihu.com/p/58692240
https://webglfundamentals.org/webgl/lessons/webgl-ramp-textures.html

Technical Artist(技术美工)
一种复合型人才,不但需du要会技术类的比如编程,游戏引擎,架构,游戏插件等还需要有美术师的背景,比如会用 maya max PS 等,这样才能为游戏开发好的插件,导入引擎等等。
高端 TA 就涉及到为游戏制作开发插件和开发引擎等等。

利用 WebGL 实现 VR
参考引用:http://www.jiazhengblog.com/blog/2017/05/05/3142/#comments

WebGL半透明物体的绘制
参考引用:https://www.cnblogs.com/edwardloveyou/p/10801422.html

全局光照(简述)
参考引用:https://blog.csdn.net/weixin_43022263/article/details/106054410

经度(longitude)与 纬度(latitude)
经度范围是-180°~180°,纬度范围是-90°~90°(其实应该是南极到赤道:90°~0°,然后从赤道到北极:0°~90°,但为了在坐标轴上描述实际情况,我们表示-90°~90°)(同理)

参考引用:https://blog.csdn.net/u011294404/article/details/53350421

墨卡托投影的变体——Web墨卡托投影(Mercator Projection)

参考引用:
Web地图呈现原理 https://www.cnblogs.com/dojo-lzz/p/9250637.html

屏幕分辨率 resolution
在 shader 里可以用于实现精确到 1 像素显示,
attribute float vertexId;
uniform float numVerts;
uniform vec2 resolution;

#define PI radians(180.0)

void main() {
float u = vertexId / numVerts; // goes from 0 to 1
float angle = u * PI * 2.0; // goes from 0 to 2PI
float radius = 0.8;

vec2 pos = vec2(cos(angle), sin(angle)) * radius;

float aspect = resolution.y / resolution.x;
vec2 scale = vec2(aspect, 1);

gl_Position = vec4(pos * scale, 0, 1);
gl_PointSize = 5.0;
}

fract 函数
效果,只取小数部分的值,实现 0 ~ 1
attribute float vertexId;
uniform float numVerts;
uniform float time;

void main() {
float u = vertexId / numVerts; // goes from 0 to 1
float x = u * 2.0 - 1.0; // -1 to 1
float y = fract(time + u) * -2.0 + 1.0; // 1.0 -> -1.0

gl_Position = vec4(x, y, 0, 1);
gl_PointSize = 5.0;
}

参考引用:
https://webglfundamentals.org/webgl/lessons/webgl-drawing-without-data.html#pointsissues

GLSL 内建函数
https://blog.csdn.net/hgl868/article/details/7876257

Shader Art
https://www.vertexshaderart.com/
https://www.shadertoy.com/

WebGL中有宽度的线
https://www.cnblogs.com/dojo-lzz/p/9461506.html
https://www.cnblogs.com/dojo-lzz/p/9219290.html

求两个向量之间的角度
描述
返回两个向量之间的角度(θ)。
提示
使用 Array.prototype.reduce(),Math.pow() 和 Math.sqrt() 分别计算每一个向量的模
使用 Array.prototype.reduce() 计算两个向量的内积
使用 Math.acos() 和公式

const vectorAngle = (x, y) => {
  let mX = Math.sqrt(x.reduce((acc, n) => acc + Math.pow(n, 2), 0));
  let mY = Math.sqrt(y.reduce((acc, n) => acc + Math.pow(n, 2), 0));
  return Math.acos(x.reduce((acc, n, i) => acc + n * y[i], 0) / (mX * mY));
};

参考引用:https://ld246.com/article/1590996307168

暮志未晚 Webgl 案例大全
参考引用:https://blog.csdn.net/qq_30100043