一、原理
目前我们是首先渲染天空盒,之后再渲染场景中的其它物体。这样子能够工作,但不是非常高效。如果我们先渲染天空盒,我们就会对屏幕上的每一个像素运行一遍片段着色器,即便只有一小部分的天空盒最终是可见的。可以使用提前深度测试(Early Depth Testing)轻松丢弃掉的片段能够节省我们很多宝贵的带宽。
所以,我们将会最后渲染天空盒,以获得轻微的性能提升。这样子的话,深度缓冲就会填充满所有物体的深度值了,我们只需要在提前深度测试通过的地方渲染天空盒的片段就可以了,很大程度上减少了片段着色器的调用。问题是,天空盒只是一个1x1x1的立方体,它很可能会不通过大部分的深度测试,导致渲染失败。不用深度测试来进行渲染不是解决方案,因为天空盒将会复写场景中的其它物体。我们需要欺骗深度缓冲,让它认为天空盒有着最大的深度值1.0,只要它前面有一个物体,深度测试就会失败。
在坐标系统小节中我们说过:
是在顶点着色器运行之后执行的,将gl_Position的xyz坐标除以w分量。我们又从深度测试小节中知道,相除结果的z分量等于顶点的深度值。使用这些信息,我们可以将输出位置的z分量等于它的w分量,让z分量永远等于1.0,这样子的话,当透视除法执行之后,z分量会变为。
首先开启提前深度测试:
gl.depthFunc(gl.LEQUAL);
顶点着色器修改
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.xyww;
二、代码
//顶点着色器
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.xyww;
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;
uniform mat4 u_ModelMatrix;
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProjectMatrix;
varying vec2 v_Uv;
void main(){
gl_Position=u_ProjectMatrix*u_ViewMatrix*u_ModelMatrix*a_Position;
v_Uv=a_Uv;
}
`
var f_shader =/*glsl*/`
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 v_Uv;
uniform sampler2D u_Texture;
void main(){
vec4 baseColor=texture2D(u_Texture,v_Uv);
gl_FragColor=baseColor;
}
`
//声明js需要的相关变量
var canvas = document.getElementById("canvas");
var gl = getWebGLContext(canvas);
var boxProgram;
var skyboxProgram
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, 0, 4, 0, 0, 0, 0, 1, 0);
//开启隐藏面清除
gl.enable(gl.DEPTH_TEST);
// gl.enable(gl.CULL_FACE)
gl.program = skyboxProgram;
gl.useProgram(skyboxProgram);
//获取内置变量的信息
getVariableLocation();
var n = createCube(gl);
if (n < 0) {
console.log("无法创建缓冲区");
return;
}
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();
var n = createCube(gl);
if (n < 0) {
console.log("无法创建缓冲区");
return;
}
//初始化纹理
await initTexture(gl, "./image/box.jpg", 0);
document.addEventListener("keydown", (e) => {
if (e.key === "ArrowUp") {
viewMatrix.translate(0, 0, 1)
} else if (e.key === "ArrowDown") {
viewMatrix.translate(0, 0, -1)
} else if (e.key === "ArrowLeft") {
viewMatrix.rotate(-1, 0, 1, 0)
} else if (e.key === "ArrowRight") {
viewMatrix.rotate(1, 0, 1, 0)
}
});
// await draw(projMatrix,viewMatrix)
//设置底色
gl.depthFunc(gl.LEQUAL);
//根据时间绘制
var tick = async function () {
await draw(projMatrix, viewMatrix)
//重复请求
requestAnimationFrame(tick)
}
tick()
}
async function draw(projMatrix, viewMatrix) {
//清空颜色和深度缓冲区
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// gl.depthMask(true);
gl.useProgram(boxProgram)
gl.program = boxProgram
await drawBox(projMatrix, viewMatrix)
// gl.depthMask(false);
gl.useProgram(skyboxProgram)
gl.program = skyboxProgram
//绘制三角形
await drawSkyBox(projMatrix, viewMatrix);
}
async function drawSkyBox(projMatrix, viewMatrix) {
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);
//绘制图形
gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_BYTE, 0);
}
async function drawBox(projMatrix, viewMatrix) {
const program = gl.program;
//设置模型矩阵的相关信息
var modelMatrix = new Matrix4();
gl.uniformMatrix4fv(program.modelMatrix, false, modelMatrix.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);
//绘制图形
gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_BYTE, 0);
}