一、原理
1.1 、反射
反射这个属性表现为物体(或物体的一部分)反射它周围环境,即根据观察者的视角,物体的颜色或多或少等于它的环境。镜子就是一个反射性物体:它会根据观察者的视角反射它周围的环境。
反射的原理并不难。下面这张图展示了我们如何计算反射向量,并如何使用这个向量来从立方体贴图中采样:
//计算反射颜色
vec3 reflectDir=reflect(viewDir,normalize(v_Normal));
vec3 reflectColor=textureCube(u_CubeMapTexture,reflectDir).rgb;
1.2、折射
环境映射的另一种形式是折射,它和反射很相似。折射是光线由于传播介质的改变而产生的方向变化。在常见的类水表面上所产生的现象就是折射,光线不是直直地传播,而是弯曲了一点。将你的半只胳膊伸进水里,观察出来的就是这种效果。
折射是通过斯涅尔定律(Snell’s Law)来描述的,使用环境贴图的话看起来像是这样:
同样,我们有一个观察向量,一个法向量,而这次是折射向量。可以看到,观察向量的方向轻微弯曲了。弯折后的向量将会用来从立方体贴图中采样。
折射可以使用GLSL的内建refract函数来轻松实现,它需要一个法向量、一个观察方向和两个材质之间的折射率(Refractive Index)。
//计算折射颜色
vec3 refractDir=refract(viewDir,normalize(v_Normal),0.75);
vec3 refractColor=textureCube(u_CubeMapTexture,refractDir).rgb;
二、效果
三、代码
//顶点着色器
var skyBox_VS = /*glsl*/ `
attribute vec4 a_Position;
uniform mat4 u_ModelMatrix;
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProjectMatrix;
varying vec3 v_Position;
void main(){
mat4 relativeViewMatrix=u_ViewMatrix;
relativeViewMatrix[3].x=0.0;
relativeViewMatrix[3].y=0.0;
relativeViewMatrix[3].z=0.0;
vec4 clipPosition=u_ProjectMatrix*relativeViewMatrix*u_ModelMatrix * a_Position;
gl_Position =clipPosition;
v_Position=a_Position.rgb;
}`;
//片元着色器
var skyBox_FS = /*glsl*/ `
#ifdef GL_ES
precision mediump float;
#endif
uniform samplerCube u_CubeMapTexture;
uniform vec3 u_AmbitionColor;
varying vec3 v_Position;
void main(){
gl_FragColor=textureCube(u_CubeMapTexture, v_Position);
}`;
var v_shader =/*glsl*/`
attribute vec4 a_Position;
attribute vec2 a_Uv;
attribute vec3 a_Normal;
uniform mat4 u_ModelMatrix;
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProjectMatrix;
uniform mat4 u_NormalMatrix;
varying vec2 v_Uv;
varying vec3 v_Normal;
varying vec3 v_Position;
void main(){
gl_Position=u_ProjectMatrix*u_ViewMatrix*u_ModelMatrix*a_Position;
v_Uv=a_Uv;
v_Normal=(u_NormalMatrix*vec4(a_Normal,1.0)).xyz;
v_Position=(u_ModelMatrix*a_Position).xyz;
}
`
var f_shader =/*glsl*/`
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 v_Uv;
varying vec3 v_Normal;
varying vec3 v_Position;
uniform sampler2D u_Texture;
uniform vec3 u_ViewPosition;
uniform samplerCube u_CubeMapTexture;
void main(){
//计算反射向量
vec3 viewDir=normalize(v_Position-u_ViewPosition);
//计算反射颜色
vec3 reflectDir=reflect(viewDir,normalize(v_Normal));
vec3 reflectColor=textureCube(u_CubeMapTexture,reflectDir).rgb;
//计算折射颜色
vec3 refractDir=refract(viewDir,normalize(v_Normal),0.75);
vec3 refractColor=textureCube(u_CubeMapTexture,refractDir).rgb;
vec4 baseColor=vec4(0.0,0.0,0.8,0.8);
// gl_FragColor=vec4(refractColor,baseColor.a);
gl_FragColor=vec4(mix(baseColor.rgb,reflectColor+refractColor,0.7),baseColor.a);
}
`
//声明js需要的相关变量
var canvas = document.getElementById("canvas");
var gl = getWebGLContext(canvas);
var boxProgram;
var skyboxProgram
var planeModelMatrix;
async function main() {
if (!gl) {
console.log("你的浏览器不支持WebGL");
return;
}
skyboxProgram = createProgram(gl, skyBox_VS, skyBox_FS);
if (!skyboxProgram) {
console.warn("创建程序失败!");
return;
}
boxProgram = createProgram(gl, v_shader, f_shader)
if (!boxProgram) {
console.warn("创建立方体程序失败!");
return;
}
//设置透视投影矩阵
var projMatrix = new Matrix4();
projMatrix.setPerspective(30, canvas.width / canvas.height, 0.1, 100);
//设置视角矩阵的相关信息(视点,视线,上方向)
var viewMatrix = new Matrix4();
viewMatrix.setLookAt(0, 2, 4, 0, 0, 0, 0, 1, 0);
planeModelMatrix = new Matrix4()
//开启隐藏面清除
gl.enable(gl.DEPTH_TEST);
// gl.enable(gl.CULL_FACE)
gl.program = skyboxProgram;
gl.useProgram(skyboxProgram);
//获取内置变量的信息
getVariableLocation();
const cube = createCube(gl);
await initCubeTexture(
[
"./image/skybox/right.jpg",
"./image/skybox/left.jpg",
"./image/skybox/top.jpg",
"./image/skybox/bottom.jpg",
"./image/skybox/front.jpg",
"./image/skybox/back.jpg",
]
)
gl.useProgram(boxProgram)
gl.program = boxProgram
//获取内置变量的信息
getVariableLocation();
const plane = createPlane(gl);
//初始化纹理
await initTexture(gl, "./image/box.jpg", 1);
// await draw(projMatrix, viewMatrix, cube, plane)
//设置底色
// gl.depthFunc(gl.LEQUAL);
//根据时间绘制
var tick = function () {
draw(projMatrix, viewMatrix, cube, plane)
//重复请求
requestAnimationFrame(tick)
}
tick()
document.addEventListener("keydown", (e) => {
if (e.key === "ArrowUp") {
planeModelMatrix.rotate(1, 1, 0, 0)
} else if (e.key === "ArrowDown") {
planeModelMatrix.rotate(-1, 1, 0, 0)
} else if (e.key === "ArrowLeft") {
planeModelMatrix.rotate(-1, 0, 1, 0)
} else if (e.key === "ArrowRight") {
planeModelMatrix.rotate(1, 0, 1, 0)
}
});
}
function draw(projMatrix, viewMatrix, cube, plane) {
//清空颜色和深度缓冲区
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.depthMask(false);
gl.useProgram(skyboxProgram)
gl.program = skyboxProgram
//绘制三角形
drawSkyBox(projMatrix, viewMatrix, cube);
gl.depthMask(true);
gl.useProgram(boxProgram)
gl.program = boxProgram
drawBox(projMatrix, viewMatrix, plane)
}
function drawSkyBox(projMatrix, viewMatrix, cube) {
const program = gl.program;
//设置视角矩阵的相关信息(视点,视线,上方向)
//将试图矩阵传给u_ViewMatrix变量
gl.uniformMatrix4fv(program.projectMatrix, false, projMatrix.elements);
//设置模型矩阵的相关信息
var modelMatrix = new Matrix4();
gl.uniformMatrix4fv(program.modelMatrix, false, modelMatrix.elements);
gl.uniformMatrix4fv(program.viewMatrix, false, viewMatrix.elements);
const arrayBuffers = cube.arrayBuffers
//写入缓冲区
arrayBuffers.forEach(arrayBuffer => {
if (arrayBuffer) {
writeAttributeVariable(gl, arrayBuffer.attribute, arrayBuffer)
}
});
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cube.indexBuffer);
//绘制图形
gl.drawElements(gl.TRIANGLES, cube.length, gl.UNSIGNED_BYTE, 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
function drawBox(projMatrix, viewMatrix, plane) {
const program = gl.program;
//设置模型矩阵的相关信息
// var modelMatrix = new Matrix4();
gl.uniformMatrix4fv(program.modelMatrix, false, planeModelMatrix.elements);
gl.uniformMatrix4fv(program.viewMatrix, false, viewMatrix.elements);
//设置透视投影矩阵
var projMatrix = new Matrix4();
projMatrix.setPerspective(30, canvas.width / canvas.height, 0.1, 100);
//将试图矩阵传给u_ViewMatrix变量
gl.uniformMatrix4fv(program.projectMatrix, false, projMatrix.elements);
//传入眼睛位置
const viewPosition = gl.getUniformLocation(program, 'u_ViewPosition')
gl.uniform3fv(viewPosition, [0, 2, 4]);
var normalMatrix = new Matrix4()
normalMatrix.transpose(normalMatrix.setInverseOf(planeModelMatrix))
gl.uniformMatrix4fv(program.normalMatrix, false, normalMatrix.elements);
const arrayBuffers = plane.arrayBuffers
arrayBuffers.forEach(arrayBuffer => {
if (arrayBuffer) {
writeAttributeVariable(gl, arrayBuffer.attribute, arrayBuffer)
}
});
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, plane.indexBuffer);
//绘制图形
gl.drawElements(gl.TRIANGLES, plane.length, gl.UNSIGNED_BYTE, 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}