一、雾化
WEBGL中的雾化是一种视觉效果,可以模拟远处物体出现“雾气”或“烟雾”等效果,使场景更加真实。雾化可以让场景中的物体看起来模糊或透明,从而创建出一种深度感和距离感,增强场景的立体感。
WEBGL中的雾化通常使用线性或指数函数来计算出物体的深度,然后根据深度来进行颜色和透明度的调整,从而实现雾化效果。调整透明度的方式有多种,例如将物体的alpha值进行平滑过渡,或者使用特定的shader程序来自定义透明度的计算方式。
在实际的WEBGL应用中,雾化可以被用于增强场景的真实感,同时也可以用于创建特定的氛围和视觉风格。
雾化的实现流程
雾化实现是通过某点和视点之间的距离,距离越远雾化程度越⾼。这种雾化也称为线性雾化。某一点的雾化程度也成为雾化因⼦,WebGL中实现雾化的一般流程如下:
- 创建shader:在WebGL中,我们使用着色器(shader)来描述如何绘制一个物体,因此,我们需要创建雾化的shader。在shader中,我们需要定义雾化程度的变量,以及计算每个像素颜色时需要使用的公式等。
- 定义雾化区域:定义哪些区域需要雾化。对于这一步,我们可以依据相机的距离,将远离相机的物体进行雾化处理,或者根据场景中物体的坐标确定需要雾化的区域。
- 计算雾化程度:计算每个像素的雾化程度,这个程度可以用雾化公式进行计算,可以使用线性雾化公式或者指数雾化公式等。
- 应用雾化:将计算得到的雾化程度,应用到相应的像素上,最终实现雾化效果。
实现雾化效果时,需要考虑场景中物体的深度,而且雾化程度也可能会随着距离的远近而变化,因此,需要综合考虑这些因素,调整雾化效果。
雾化因子计算:雾化因子 = (终点 - 当前点) / (终点 - 起点)
物体颜色计算:颜色 = 物体颜色 * 雾化因子 + 雾化颜色 * ( 1 - 雾化因子)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>雾化</title>
<script src="../../lib/index.js"></script>
<style>
* {
margin: 0;
padding: 0;
}
canvas{
margin: 50px auto 0;
display: block;
background: yellow;
}
</style>
</head>
<body>
<canvas id="canvas" width="400" height="400">
此浏览器不支持canvas
</canvas>
</body>
</html>
<script>
const ctx = document.getElementById('canvas')
const gl = ctx.getContext('webgl')
// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
attribute vec4 aPosition;
attribute vec4 aNormal;
varying vec4 vColor;
varying float vDist;
uniform mat4 mat;
void main() {
// 定义点光源的颜色
vec3 uPointLightColor = vec3(1.0,1.0,0.0);
// 点光源的位置
vec3 uPointLightPosition = vec3(-5.2,5.6,5.0);
// 环境光
vec3 uAmbientLightColor = vec3(0.2,0.2,0.2);
// 物体表面的颜色
vec4 aColor = vec4(1.0,0.0,0.0,1.0);
// 顶点的世界坐标
vec4 vertexPosition = mat * aPosition;
// 点光源的方向
vec3 lightDirection = normalize(uPointLightPosition - vec3(vertexPosition));
// 环境反射
vec3 ambient = uAmbientLightColor * vec3(aColor);
// 计算入射角 光线方向和法线方向的点积
float dotDeg = dot(lightDirection, vec3(aNormal));
// 漫反射光的颜色
vec3 diffuseColor = uPointLightColor * vec3(aColor) * dotDeg;
gl_Position = vertexPosition;
vColor = vec4(ambient + diffuseColor, aColor.a);
vDist = gl_Position.w;
}
`; // 顶点着色器
const FRAGMENT_SHADER_SOURCE = `
precision lowp float;
varying vec4 vColor;
varying float vDist;
// 雾化颜色
uniform vec3 uFogColor;
// 起点到终点的距离 第一个参数是起点 第二个是终点
uniform vec2 uFogDist;
void main() {
// 计算雾化因子
float fogFactor = (uFogDist.y - vDist) / (uFogDist.y - uFogDist.x);
// 看到的颜色是什么 物体颜色*雾化因子+雾化颜色*(1-雾化因子)
// mix 线性混合计算 mix(x,y,a) => { x * (1-a) + y * a }
vec3 color = mix(uFogColor, vec3(vColor), fogFactor);
gl_FragColor = vec4(color, vColor.a);
}
`; // 片元着色器
const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)
const aPosition = gl.getAttribLocation(program, 'aPosition');
const aNormal = gl.getAttribLocation(program, 'aNormal');
const mat = gl.getUniformLocation(program, 'mat');
const vertices = new Float32Array([
// 0123
1, 1, 1,
-1, 1, 1,
-1,-1, 1,
1,-1, 1,
// 0345
1, 1, 1,
1,-1, 1,
1,-1,-1,
1, 1,-1,
// 0156
1, 1, 1,
1, 1, -1,
-1, 1,-1,
-1, 1,1,
// 1267
-1, 1, 1,
-1,1, -1,
-1, -1,-1,
-1,-1,1,
// 2347
-1,-1, 1,
1,-1, 1,
1,-1,-1,
-1,-1,-1,
// 4567
1,-1,-1,
1, 1,-1,
-1, 1,-1,
-1,-1,-1,
])
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
gl.vertexAttribPointer(aPosition, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(aPosition)
// 法向量
const 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,
0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,
-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,
1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,
0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,
0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,
])
const normalBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
gl.bufferData(gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW);
gl.vertexAttribPointer(aNormal, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(aNormal)
const indeces = new Uint8Array([
0,1,2,0,2,3,
4,5,6,4,6,7,
8,9,10,8,10,11,
12,13,14,12,14,15,
16,17,18,16,18,19,
20,21,22,20,22,23,
])
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indeces, gl.STATIC_DRAW);
const start = 0;
const end = 200;
const fogColor = new Float32Array([0.0,0.0,0.0]);
const fogDist = new Float32Array([start, end]);
const uFogColor = gl.getUniformLocation(program, 'uFogColor');
const uFogDist = gl.getUniformLocation(program, 'uFogDist');
gl.uniform3fv(uFogColor, fogColor);
const vm = getViewMatrix(3,3,5,0.0,0.0,0.0,0.0,0.6,0.0);
const perspective = getPerspectiveMatrix(30, ctx.width / ctx.height, 100, 1);
gl.enable(gl.DEPTH_TEST);
gl.uniformMatrix4fv(mat, false, mixMatrix(perspective, vm));
function draw() {
fogDist[1] -= 1;
if (fogDist[1] < start) {
fogDist[1] = end;
}
gl.uniform2fv(uFogDist, fogDist);
gl.clearColor(0.0,0.0,0.0,1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
gl.drawElements(gl.TRIANGLES, indeces.length, gl.UNSIGNED_BYTE, 0);
requestAnimationFrame(draw)
}
draw();
</script>
二、半透明
半透明物体是指在光线透过时,可以看到模糊的影像,但不完全透明的物体。这种物体通常具有一定的透明性,但不完全透明,仍然存在某种程度上的不透明性。
半透明物体的意义在于它可以在不影响视线的情况下,让光线穿过物体,使得我们能够观察到物体背后或者内部的部分。这种特性在许多应用中都非常有用,比如建筑物中的玻璃窗、医疗设备中的透明塑料、以及电子设备中的显示屏。此外,半透明物体还具有一定的艺术效果,可以让物体呈现出半透明的质感和神秘感,增强视觉体验。
半透明的实现流程
- 在片元着色器中定义透明度属性,并使用gl_FragColor来设置颜色和透明度值。
- 使用gl.enable(gl.BLEND)启用混合模式,这样可以将透明物体正确地混合到场景中。
- 使用gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)设置混合函数,指定源颜色因子和目标颜色因子,以便正确地混合透明物体。
- 在渲染透明物体时,确保先渲染不透明的物体,再渲染半透明的物体。这样可以避免混合时受到前面物体的影响而导致不正确的结果。
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA):源因子和目标因子是可以通过gl.blendFunc函数来进行设置的,gl.blendFunc有两个参数,前者表示源因子,后者表示目标因子。这两个参数可以是多种值,下面介绍比较常用的几种。
gl.ZERO:表示使用0.0作为因子,实际上相当于不使用这种颜色参与混合运算。
gl.ONE:表示使用1.0作为因子,实际上相当于完全的使用了这种颜色参与混合运算。
gl.SRC_ALPHA:表示使用源颜色的alpha值来作为因子。
gl.DST_ALPHA:表示使用目标颜色的alpha值来作为因子。
gl.ONE_MINUS_SRC_ALPHA:表示用1.0减去源颜色的alpha值来作为因子。
gl.ONE_MINUS_DST_ALPHA:表示用1.0减去目标颜色的alpha值来作为因子。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>半透明</title>
<script src="../../lib/index.js"></script>
<style>
* {
margin: 0;
padding: 0;
}
canvas{
margin: 50px auto 0;
display: block;
background: yellow;
}
</style>
</head>
<body>
<canvas id="canvas" width="400" height="400">
此浏览器不支持canvas
</canvas>
</body>
</html>
<script>
const ctx = document.getElementById('canvas')
const gl = ctx.getContext('webgl')
// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
attribute vec4 aPosition;
attribute vec4 aNormal;
varying vec4 vColor;
uniform mat4 mat;
void main() {
// 定义点光源的颜色
vec3 uPointLightColor = vec3(1.0,1.0,0.0);
// 点光源的位置
vec3 uPointLightPosition = vec3(-5.0,6.0,10.0);
// 环境光
vec3 uAmbientLightColor = vec3(0.2,0.2,0.2);
// 物体表面的颜色
vec4 aColor = vec4(1.0,0.0,0.0,1.0);
// 顶点的世界坐标
vec4 vertexPosition = mat * aPosition;
// 点光源的方向
vec3 lightDirection = normalize(uPointLightPosition - vec3(vertexPosition));
// 环境反射
vec3 ambient = uAmbientLightColor * vec3(aColor);
// 计算入射角 光线方向和法线方向的点积
float dotDeg = dot(lightDirection, vec3(aNormal));
// 漫反射光的颜色
vec3 diffuseColor = uPointLightColor * vec3(aColor) * dotDeg;
gl_Position = vertexPosition;
vColor = vec4(ambient + diffuseColor, 0.5);
}
`; // 顶点着色器
const FRAGMENT_SHADER_SOURCE = `
precision lowp float;
varying vec4 vColor;
void main() {
gl_FragColor = vColor;
}
`; // 片元着色器
const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)
const aPosition = gl.getAttribLocation(program, 'aPosition');
const aNormal = gl.getAttribLocation(program, 'aNormal');
const mat = gl.getUniformLocation(program, 'mat');
const vertices = new Float32Array([
// 0123
1, 1, 1,
-1, 1, 1,
-1,-1, 1,
1,-1, 1,
// 0345
1, 1, 1,
1,-1, 1,
1,-1,-1,
1, 1,-1,
// 0156
1, 1, 1,
1, 1, -1,
-1, 1,-1,
-1, 1,1,
// 1267
-1, 1, 1,
-1,1, -1,
-1, -1,-1,
-1,-1,1,
// 2347
-1,-1, 1,
1,-1, 1,
1,-1,-1,
-1,-1,-1,
// 4567
1,-1,-1,
1, 1,-1,
-1, 1,-1,
-1,-1,-1,
])
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
gl.vertexAttribPointer(aPosition, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(aPosition)
// 法向量
const 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,
0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,
-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,
1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,
0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,
0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,
])
const normalBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
gl.bufferData(gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW);
gl.vertexAttribPointer(aNormal, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(aNormal)
const indeces = new Uint8Array([
0,1,2,0,2,3,
4,5,6,4,6,7,
8,9,10,8,10,11,
12,13,14,12,14,15,
16,17,18,16,18,19,
20,21,22,20,22,23,
])
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indeces, gl.STATIC_DRAW);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
const vm = getViewMatrix(3,3,5,0.0,0.0,0.0,0.0,0.6,0.0);
const perspective = getPerspectiveMatrix(30, ctx.width / ctx.height, 100, 1);
gl.uniformMatrix4fv(mat, false, mixMatrix(perspective, vm));
gl.drawElements(gl.TRIANGLES, indeces.length, gl.UNSIGNED_BYTE, 0);
</script>