Appearance
三维正交投影
前言
从本节开始,我们就要进入三维的世界中了,准备好了吗?Let's go!
不过在这之前,我们需要先解决一下在 仿射变换一章中的遗留问题。还记得最后我们实现的 demo 中,三角形在旋转的时候,它的形状也开始逐渐“变形”了吗?
我们现在先来解决这个问题。要解决它,就要理解这个问题是如何产生的。其原因是因为:我们是直接在归一化的坐标中直接进行的旋转,也就是是说,无论我们的画布尺寸是多少,画布的最左边和最下边的值都是-1,最上边和最右边的值都是 1。所以,当我们旋转时坐标发生变化,映射到画布上的位置时就发生了变形。那要解决这个问题,我们需要在画布真实尺寸的坐标系下来进行旋转,再将其转换到-1~1 的坐标范围之间。
也就是说,需要在数学上满足转换:
简单的推导一下 x 变换到 x'的过程
所以:
对于 y 坐标与 z 坐标,同理的有: ,
我们将其转换成矩阵,可以写为:
上面这个矩阵,我们将其称为投影矩阵。让我们稍微的修改一下仿射变换例子中的顶点着色器的代码。
glsl
attribute vec4 a_position;
uniform mat4 u_translate;
uniform mat4 u_rotate;
uniform mat4 u_scale;
uniform mat4 u_proj;
void main () {
// gl_Position = u_translate * u_rotate * u_scale * a_position;
gl_Position = u_proj * u_translate * u_rotate * u_scale * a_position;
}
除此之外,我们的顶点数据也需要修改一下,我们的顶点数据的范围不再是 -1~1 了,而是 0~width, 0~height。
最后我们还需要往 u_proj
中传入我们的投影矩阵。
TIP
我们可以利用 gl-matrix
库中提供的 mat4.ortho
方法来快速的生成这个矩阵。
ts
const pointPos = [-0.5, 0.0, 0.5, 0.0, 0.0, 0.5];
const pointPos = [-0.0, 0.0, 200, 0.0, 100.0, 200];
// ......
const uProj = gl.getUniformLocation(program, 'u_proj');
const projMat = mat4.create();
mat4.ortho(projMat, 0, canvas.width, 0, canvas.height, -1, 100);
gl.uniformMatrix4fv(uProj, false, projMat);
TranslateX
TranslateY
Rotation
Scale
在上面的 Demo 中,我们可以看到,当我们旋转时,三角形不再发生“形变”了。
正交投影
我们已经完成了在 2D 平面中的变换,在 3 维世界中同样也是类似的,只不过我们多了一个 z
坐标。想象我们的三维空间是一个长为width
,高为 height
,深为depth
的立方体。
还记得上面我们的 矩阵吗?我们不需要对它进行任何修改。我现在只需要修改我们顶点数据。现在我们准备绘制一个立方体,我们开始修改我们的顶点数据。
ts
//prettier-ignore
const pointPos = [
// front-face
0, 0, 0, width, 0, 0, width, height, 0, width, height, 0, 0, height, 0, 0, 0, 0,
// back-face
0, 0, depth, width, 0, depth, width, height, depth, width, height, depth, 0, height, depth, 0, 0, depth,
// left-face
0, 0, 0, 0, height, 0, 0, height, depth, 0, height, depth, 0, 0, depth, 0, 0, 0,
// right-face
width, 0, 0, width, height, 0, width, height, depth, width, height, depth, width, 0, depth, width, 0, 0,
// top-face
0, height, 0, width, height, 0, width, height, depth, width, height, depth, 0, height, depth, 0, height, 0,
// bottom-face
0, 0, 0, width, 0, 0, width, 0, depth, width, 0, depth, 0, 0, depth, 0, 0, 0,
];
//prettier-ignore
const colors = [
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0,
1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1,
0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1,
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
]
我们除了修改顶点的位置数据之外,我们为了更好的展示立方体,还额外的为每个顶点提供了颜色信息。往顶点中传入颜色数据的方法与传入位置信息类似。
ts
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
const a_color = gl.getAttribLocation(program, 'a_color');
// 我们不再采用这种方式进行传值
gl.vertexAttribPointer(
a_color,
3,
gl.FLOAT,
false,
Float32Array.BYTES_PER_ELEMENT * 3,
0
);
gl.enableVertexAttribArray(a_color);
TIP
另外,之前我们使用 vertexAttribPointer
这个 API 时,其中的第二个参数,原来是 2
。现在由于我们是 3D 图形,我们每个坐标是 3 维向量,所以我们需要修改为 3。
更多细节
绘制 3D 图形与 2D 图形还有很多不同,我们在这里列举 2 个容易被忽略的地方。就是深度缓冲
和剔除面
。
我们简单的介绍一下深度缓冲区和剔除面
深度缓冲区
深度缓冲区是 WebGL 中用来存储每个像素的深度值(与摄像机的距离)的一种缓冲区。它可以用来判断哪些物体在前面,哪些物体在后面,从而实现隐藏面消除的效果。
要使用深度缓冲区,需要先通过这个 API 启用它gl.enable(gl.DEPTH_TEST)
值得注意的是,一般每次绘制的时候我们都需要将之前的深度缓冲区清除。我们使用 gl.clear(gl.DEPTH_BUFFER_BIT)
。但是我们与此同时还需要清空颜色缓冲区,我们使用位运算符号 '|' 来同时清除它们。gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
。
剔除面
在 WebGL 中的三角形有正反面的概念,正面三角形的顶点顺序是逆时针方向, 反面三角形是顺时针方向。
WebGL 可以只绘制正面或反面三角形。如果你要使用这个特性,需要通过 API gl.enable(gl.CULL_FACE)
来开启。 WebGL 默认剔除背面的三角形,也就是说三角形的顶点顺序是顺时针的的话,则该三角形不会被绘制。
运用剔除面技术,可以提高 WebGL 的性能。
其余的程序与之前的代码几乎无异。你可以通过 Demo 文末的代码进行对比。
TranslateX
TranslateY
RotationZ
RotationY
RotationX
Scale
如果觉得本文有用,可以请作者喝杯咖啡~
ts
const canvas = document.getElementById('canvas2') as HTMLCanvasElement;
const gl = canvas.getContext('webgl');
if (!gl) {
return null;
}
// 设置清空颜色缓冲区时的颜色
gl.clearColor(1.0, 1.0, 1.0, 1.0);
// 清空颜色缓冲区
gl.clear(gl.COLOR_BUFFER_BIT);
// 顶点着色器
const vertexShader = `
attribute vec4 a_position;
attribute vec3 a_color;
uniform mat4 u_translate; //
uniform mat4 u_rotate; //
uniform mat4 u_scale; //
uniform mat4 u_proj;
varying vec3 v_color;
void main () {
gl_Position = u_proj * u_translate * u_rotate * u_scale * a_position; //
v_color = a_color;
}
`;
// 片元着色器
const fragmentShader = `
// 设置浮点数精度
precision mediump float;
varying vec3 v_color;
void main () {
// vec4是表示四维向量,这里用来表示RGBA的值[0~1],均为浮点数,如为整数则会报错
gl_FragColor = vec4(v_color, 1.0);
}
`;
// 初始化shader程序
const program = initWebGL(gl, vertexShader, fragmentShader);
if (!program) {
return null;
}
// 告诉WebGL使用我们刚刚初始化的这个程序
gl.useProgram(program);
gl.enable(gl.DEPTH_TEST);
const width = 100;
const height = 100;
const depth = -100;
//prettier-ignore
const pointPos = [
// front-face
0, 0, 0, width, 0, 0, width, height, 0, width, height, 0, 0, height, 0, 0, 0, 0,
// back-face
0, 0, depth, width, 0, depth, width, height, depth, width, height, depth, 0, height, depth, 0, 0, depth,
// left-face
0, 0, 0, 0, height, 0, 0, height, depth, 0, height, depth, 0, 0, depth, 0, 0, 0,
// right-face
width, 0, 0, width, height, 0, width, height, depth, width, height, depth, width, 0, depth, width, 0, 0,
// top-face
0, height, 0, width, height, 0, width, height, depth, width, height, depth, 0, height, depth, 0, height, 0,
// bottom-face
0, 0, 0, width, 0, 0, width, 0, depth, width, 0, depth, 0, 0, depth, 0, 0, 0,
];
//prettier-ignore
const colors = [
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0,
1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1,
0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1,
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
]
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(pointPos), gl.STATIC_DRAW);
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
// 获取shader中a_position的地址
const a_position = gl.getAttribLocation(program, 'a_position');
// 我们不再采用这种方式进行传值
// gl.vertexAttrib3f(a_position, 0.0, 0.0, 0.0);
// 采用vertexAttribPointer进行传值
gl.vertexAttribPointer(
a_position,
3,
gl.FLOAT,
false,
Float32Array.BYTES_PER_ELEMENT * 3,
0
);
gl.enableVertexAttribArray(a_position);
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
const a_color = gl.getAttribLocation(program, 'a_color');
// 我们不再采用这种方式进行传值
gl.vertexAttribPointer(
a_color,
3,
gl.FLOAT,
false,
Float32Array.BYTES_PER_ELEMENT * 3,
0
);
gl.enableVertexAttribArray(a_color);
// 我们需要往shader中传入矩阵
const uTranslateLoc = gl.getUniformLocation(program, 'u_translate'); //
const uRotateLoc = gl.getUniformLocation(program, 'u_rotate'); //
const uScaleLoc = gl.getUniformLocation(program, 'u_scale'); //
let translateX = 0; //
let translateY = 0; //
let rotateRadian = 0; //
let scale = 1; //
let radianY = 1;
let radianX = 0;
const uProj = gl.getUniformLocation(program, 'u_proj');
const projMat = mat4.create();
mat4.ortho(projMat, 0, canvas.width, 0, canvas.height, -200, 200);
gl.uniformMatrix4fv(uProj, false, projMat);
const render = () => {
gl.useProgram(program);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); //
const translateMat = mat4.create(); //
const rotateMat = mat4.create(); //
const scaleMat = mat4.create(); //
mat4.translate(translateMat, translateMat, [translateX, translateY, 0]); //
mat4.rotate(rotateMat, rotateMat, radianY, [0, 1, 0]);
mat4.rotate(rotateMat, rotateMat, rotateRadian, [0, 0, 1]);
mat4.rotate(rotateMat, rotateMat, radianX, [1, 0, 0]);
mat4.scale(scaleMat, scaleMat, [scale, scale, scale]); //
gl.uniformMatrix4fv(uTranslateLoc, false, translateMat); //
gl.uniformMatrix4fv(uRotateLoc, false, rotateMat); //
gl.uniformMatrix4fv(uScaleLoc, false, scaleMat); //
gl.drawArrays(gl.TRIANGLES, 0, pointPos.length / 3);
};
render();
ts
import { mat4, vec3 } from 'gl-matrix';
import { Camera, Matrix4, Object3D, PerspectiveCamera, Vector3 } from 'three';
function createShader(gl: WebGLRenderingContext, type: number, source: string) {
// 创建 shader 对象
const shader = gl.createShader(type);
// 往 shader 中传入源代码
gl.shaderSource(shader!, source);
// 编译 shader
gl.compileShader(shader!);
// 判断 shader 是否编译成功
const success = gl.getShaderParameter(shader!, gl.COMPILE_STATUS);
if (success) {
return shader;
}
// 如果编译失败,则打印错误信息
console.log(gl.getShaderInfoLog(shader!));
gl.deleteShader(shader);
}
function createProgram(
gl: WebGLRenderingContext,
vertexShader: WebGLShader,
fragmentShader: WebGLShader
): WebGLProgram | null {
// 创建 program 对象
const program = gl.createProgram();
// 往 program 对象中传入 WebGLShader 对象
gl.attachShader(program!, vertexShader);
gl.attachShader(program!, fragmentShader);
// 链接 program
gl.linkProgram(program!);
// 判断 program 是否链接成功
const success = gl.getProgramParameter(program!, gl.LINK_STATUS);
if (success) {
return program;
}
// 如果 program 链接失败,则打印错误信息
console.log(gl.getProgramInfoLog(program!));
gl.deleteProgram(program);
return null;
}
export function initWebGL(
gl: RenderContext,
vertexSource: string,
fragmentSource: string
) {
// 根据源代码创建顶点着色器
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);
// 根据源代码创建片元着色器
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
// 创建 WebGLProgram 程序
const program = createProgram(gl, vertexShader!, fragmentShader!);
return program;
}
export enum REPEAT_MODE {
NONE,
REPEAT,
MIRRORED_REPEAT,
}
export function createTexture(gl: WebGLRenderingContext, repeat?: REPEAT_MODE) {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
let mod: number = gl.CLAMP_TO_EDGE;
switch (repeat) {
case REPEAT_MODE.REPEAT:
mod = gl.REPEAT;
break;
case REPEAT_MODE.MIRRORED_REPEAT:
mod = gl.MIRRORED_REPEAT;
break;
}
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, mod);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, mod);
return texture;
}
export function isMobile(): boolean {
if (typeof window !== 'undefined' && window.navigator) {
const userAgent = window.navigator.userAgent;
return /(mobile)/i.test(userAgent);
}
return false;
}
export function clamp(x: number, min: number, max: number) {
if (x < min) {
x = min;
} else if (x > max) {
x = max;
}
return x;
}
export function readLUTCube(file: string): {
size: number;
data: number[];
} {
let lineString = '';
let isStart = true;
let size = 0;
let i = 0;
let result: number[] = [];
const processToken = (token: string) => {
if (token === 'LUT size') {
i++;
let sizeStart = false;
let sizeStr = '';
while (file[i] !== '\n') {
if (file[i - 1] === ' ' && /\d/.test(file[i])) {
sizeStart = true;
sizeStr += file[i];
} else if (sizeStart) {
sizeStr += file[i];
}
i++;
}
size = +sizeStr;
result = new Array(size * size * size);
} else if (token === 'LUT data points') {
// 读取数据
i++;
let numStr = '';
let count = 0;
while (i < file.length) {
if (/\s|\n/.test(file[i])) {
result[count++] = +numStr;
numStr = '';
} else if (/\d|\./.test(file[i])) {
numStr += file[i];
}
i++;
}
}
};
for (; i < file.length; i++) {
if (file[i] === '#') {
isStart = true;
} else if (isStart && file[i] === '\n') {
processToken(lineString);
lineString = '';
isStart = false;
} else if (isStart) {
lineString += file[i];
}
}
return {
size,
data: result,
};
}
export async function loadImages(srcs: string[]): Promise<HTMLImageElement[]> {
const all: Promise<HTMLImageElement>[] = srcs.map(item => loadImage(item));
return Promise.all(all);
}
export async function loadImage(src: string) {
return new Promise<HTMLImageElement>(resolve => {
const img = new Image();
img.src = src;
img.onload = () => {
resolve(img);
};
});
}
export function compute8ssedt(image: ImageData): number[][] {
// Initialize distance transform image
const distImage: number[][] = [];
for (let i = 0; i < image.height; i++) {
distImage[i] = [];
for (let j = 0; j < image.width; j++) {
distImage[i][j] = 0;
}
}
// Initialize queue for distance transform
const queue: number[][] = [];
const data = image.data;
for (let i = 0; i < image.height; i++) {
for (let j = 0; j < image.width; j++) {
const index = (i * image.width + j) * 4;
if (data[index] == 255) {
queue.push([i, j]);
}
}
}
// Compute distance transform
while (queue.length > 0) {
const p = queue.shift()!;
const x = p[0];
const y = p[1];
let minDist = Number.MAX_SAFE_INTEGER;
let minDir = [-1, -1];
// Compute distance to nearest foreground pixel in 8 directions
for (let i = -1; i <= 1; i++) {
for (let j = -1; j <= 1; j++) {
if (i == 0 && j == 0) continue;
const nx = x + i;
const ny = y + j;
if (
nx >= 0 &&
nx < image.height &&
ny >= 0 &&
ny < image.width
) {
const d = distImage[nx][ny] + Math.sqrt(i * i + j * j);
if (d < minDist) {
minDist = d;
minDir = [i, j];
}
}
}
}
// Update distance transform image and queue
distImage[x][y] = minDist;
if (minDir[0] != -1 && minDir[1] != -1) {
const nx = x + minDir[0];
const ny = y + minDir[1];
if (distImage[nx][ny] == 0) {
queue.push([nx, ny]);
}
}
}
return distImage;
}
// #region createFramebuffer
export function createFramebufferAndTexture(
gl: WebGLRenderingContext,
width: number,
height: number
): [WebGLFramebuffer | null, WebGLTexture | null] {
const framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
const texture = createTexture(gl, REPEAT_MODE.NONE);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
width,
height,
0,
gl.RGBA,
gl.UNSIGNED_BYTE,
null
);
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
texture,
0
);
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (status === gl.FRAMEBUFFER_COMPLETE) {
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.bindTexture(gl.TEXTURE_2D, null);
return [framebuffer, texture];
}
return [null, null];
}
// #endregion createFramebuffer
// #region lookat
export function lookAt(cameraPos: vec3, targetPos: vec3): mat4 {
const z = vec3.create();
const y = vec3.fromValues(0, 1, 0);
const x = vec3.create();
vec3.sub(z, cameraPos, targetPos);
vec3.normalize(z, z);
vec3.cross(x, y, z);
vec3.normalize(x, x);
vec3.cross(y, z, x);
vec3.normalize(y, y);
// prettier-ignore
return mat4.fromValues(
x[0], x[1], x[2], 0,
y[0], y[1], y[2], 0,
z[0], z[1], z[2], 0,
cameraPos[0], cameraPos[1], cameraPos[2], 1
);
}
// #endregion lookat
export function ASSERT(v: any) {
if (v === void 0 || v === null || isNaN(v)) {
throw new Error(v + 'is illegal value');
}
}
const lightAttenuationTable: Record<string, number[]> = {
'7': [1, 0.7, 1.8],
'13': [1, 0.35, 0.44],
'20': [1, 0.22, 0.2],
'32': [1, 0.14, 0.07],
'50': [1, 0.09, 0.032],
'65': [1, 0.07, 0.017],
'100': [1, 0.045, 0.0075],
'160': [1, 0.027, 0.0028],
'200': [1, 0.022, 0.0019],
'325': [1, 0.014, 0.0007],
'600': [1, 0.007, 0.0002],
'3250': [1, 0.0014, 0.000007],
};
// #region attenuation
export function lightAttenuationLookUp(dist: number): number[] {
const distKeys = Object.keys(lightAttenuationTable);
const first = +distKeys[0];
if (dist <= first) {
return lightAttenuationTable['7'];
}
for (let i = 0; i < distKeys.length - 1; i++) {
const key = distKeys[i];
const nextKey = distKeys[i + 1];
if (+key <= dist && dist < +nextKey) {
const value = lightAttenuationTable[key];
const nextValue = lightAttenuationTable[nextKey];
const k = (dist - +key) / (+nextKey - +key);
const kl = value[1] + (nextValue[1] - value[1]) * k;
const kq = value[2] + (nextValue[2] - value[2]) * k;
return [1, kl, kq];
}
}
return lightAttenuationTable['3250'];
}
// #endregion attenuation
// #region lesscode
export type BufferInfo = {
name: string;
buffer: WebGLBuffer;
numComponents: number;
isIndices?: boolean;
};
export function createBufferInfoFromArrays(
gl: RenderContext,
arrays: {
name: string;
numComponents: number;
data: Iterable<number>;
isIndices?: boolean;
}[]
): BufferInfo[] {
const result: BufferInfo[] = [];
for (let i = 0; i < arrays.length; i++) {
const buffer = gl.createBuffer();
if (!buffer) {
continue;
}
result.push({
name: arrays[i].name,
buffer: buffer,
numComponents: arrays[i].numComponents,
isIndices: arrays[i].isIndices,
});
if (arrays[i].isIndices) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
gl.bufferData(
gl.ELEMENT_ARRAY_BUFFER,
new Uint32Array(arrays[i].data),
gl.STATIC_DRAW
);
} else {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array(arrays[i].data),
gl.STATIC_DRAW
);
}
}
return result;
}
export type AttributeSetters = Record<string, (bufferInfo: BufferInfo) => void>;
export function createAttributeSetter(
gl: RenderContext,
program: WebGLProgram
): AttributeSetters {
const createAttribSetter = (index: number) => {
return function (b: BufferInfo) {
if (!b.isIndices) {
gl.bindBuffer(gl.ARRAY_BUFFER, b.buffer);
gl.enableVertexAttribArray(index);
gl.vertexAttribPointer(
index,
b.numComponents,
gl.FLOAT,
false,
0,
0
);
}
};
};
const attribSetter: AttributeSetters = {};
const numAttribs = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
for (let i = 0; i < numAttribs; i++) {
const attribInfo = gl.getActiveAttrib(program, i);
if (!attribInfo) {
break;
}
const index = gl.getAttribLocation(program, attribInfo.name);
attribSetter[attribInfo.name] = createAttribSetter(index);
}
return attribSetter;
}
export type UniformSetters = Record<string, (v: any) => void>;
export function createUniformSetters(
gl: RenderContext,
program: WebGLProgram
): UniformSetters {
let textUnit = 0;
const createUniformSetter = (
program: WebGLProgram,
uniformInfo: {
name: string;
type: number;
}
): ((v: any) => void) => {
const location = gl.getUniformLocation(program, uniformInfo.name);
const type = uniformInfo.type;
if (type === gl.FLOAT) {
return function (v: number) {
gl.uniform1f(location, v);
};
} else if (type === gl.FLOAT_VEC2) {
return function (v: number[]) {
gl.uniform2fv(location, v);
};
} else if (type === gl.FLOAT_VEC3) {
return function (v: number[]) {
gl.uniform3fv(location, v);
};
} else if (type === gl.FLOAT_VEC4) {
return function (v: number[]) {
gl.uniform4fv(location, v);
};
} else if (type === gl.FLOAT_MAT2) {
return function (v: number[]) {
gl.uniformMatrix2fv(location, false, v);
};
} else if (type === gl.FLOAT_MAT3) {
return function (v: number[]) {
gl.uniformMatrix3fv(location, false, v);
};
} else if (type === gl.FLOAT_MAT4) {
return function (v: number[]) {
gl.uniformMatrix4fv(location, false, v);
};
} else if (type === gl.SAMPLER_2D) {
const currentTexUnit = textUnit;
++textUnit;
return function (v: WebGLTexture) {
gl.uniform1i(location, currentTexUnit);
gl.activeTexture(gl.TEXTURE0 + currentTexUnit);
gl.bindTexture(gl.TEXTURE_2D, v);
};
}
return function () {
throw new Error('cannot find corresponding type of value.');
};
};
const uniformsSetters: UniformSetters = {};
const numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
for (let i = 0; i < numUniforms; i++) {
const uniformInfo = gl.getActiveUniform(program, i);
if (!uniformInfo) {
break;
}
let name = uniformInfo.name;
if (name.substr(-3) === '[0]') {
name = name.substr(0, name.length - 3);
}
uniformsSetters[uniformInfo.name] = createUniformSetter(
program,
uniformInfo
);
}
return uniformsSetters;
}
export function setAttribute(
attribSetters: AttributeSetters,
bufferInfos: BufferInfo[]
) {
for (let i = 0; i < bufferInfos.length; i++) {
const info = bufferInfos[i];
const setter = attribSetters[info.name];
setter && setter(info);
}
}
export function setUniform(
uniformSetters: UniformSetters,
uniforms: Record<string, any>
): void {
const keys = Object.keys(uniforms);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const v = uniforms[key];
const setter = uniformSetters[key];
setter && setter(v);
}
}
// #endregion lesscode
export function fromViewUp(view: Vector3, up?: Vector3): Matrix4 {
up = up || new Vector3(0, 1, 0);
const xAxis = new Vector3().crossVectors(up, view);
xAxis.normalize();
const yAxis = new Vector3().crossVectors(view, xAxis);
yAxis.normalize();
// prettier-ignore
return new Matrix4(
xAxis.x, yAxis.x, view.x, 0,
xAxis.y, yAxis.y, view.y, 0,
xAxis.z, yAxis.z, view.z, 0,
0, 0, 0, 1
)
}
export function getMirrorPoint(
p: Vector3,
n: Vector3,
origin: Vector3
): Vector3 {
const op = p.clone().sub(origin);
const normalizedN = n.clone().normalize();
const d = op.dot(normalizedN);
const newP = op.sub(normalizedN.multiplyScalar(2 * d));
return newP;
}
export function getMirrorVector(p: Vector3, n: Vector3): Vector3 {
const normalizedN = n.clone().normalize();
const d = p.dot(normalizedN);
return normalizedN.multiplyScalar(2 * d).sub(p);
}
export function setReflection2(
mainCamera: Camera,
virtualCamera: Camera,
reflector: Object3D
): void {
const reflectorWorldPosition = new Vector3();
const cameraWorldPosition = new Vector3();
reflectorWorldPosition.setFromMatrixPosition(reflector.matrixWorld);
cameraWorldPosition.setFromMatrixPosition(mainCamera.matrixWorld);
const rotationMatrix = new Matrix4();
rotationMatrix.extractRotation(reflector.matrixWorld);
const normal = new Vector3();
normal.set(0, 0, 1);
normal.applyMatrix4(rotationMatrix);
const view = new Vector3();
view.subVectors(reflectorWorldPosition, cameraWorldPosition);
view.reflect(normal).negate();
view.add(reflectorWorldPosition);
rotationMatrix.extractRotation(mainCamera.matrixWorld);
const lookAtPosition = new Vector3();
lookAtPosition.set(0, 0, -1);
lookAtPosition.applyMatrix4(rotationMatrix);
lookAtPosition.add(cameraWorldPosition);
const target = new Vector3();
target.subVectors(reflectorWorldPosition, lookAtPosition);
target.reflect(normal).negate();
target.add(reflectorWorldPosition);
virtualCamera.position.copy(view);
virtualCamera.position.copy(view);
virtualCamera.up.set(0, 1, 0);
virtualCamera.up.applyMatrix4(rotationMatrix);
virtualCamera.up.reflect(normal);
virtualCamera.isCamera = true;
virtualCamera.lookAt(target);
if (
virtualCamera instanceof PerspectiveCamera &&
mainCamera instanceof PerspectiveCamera
) {
virtualCamera.far = mainCamera.far; // Used in WebGLBackground
virtualCamera.updateMatrixWorld();
virtualCamera.projectionMatrix.copy(mainCamera.projectionMatrix);
} else {
// reflectCamera.updateMatrixWorld();
}
}