原生WebGL绘制阴影技术
一、本节概述:
这一节我们来研究如何实现阴影。首先,实现阴影有很多方法,本节所介绍的方法采用的是阴影贴图(shadow map),或称为深度贴图(depth map)。
二、本节知识点:
如何实现阴影
实现阴影的基本原理:太阳看不见阴影。如果在光源出放置一位观察者,其视线方向与光线一致,那么观察者也看不到阴影。他看到的每一处都在光的照射下,而那些背后的,他没有看到的物体则处在阴影中。这里,我们需要用到光源与物体之间的距离(实际上也就是物体在光源坐标系下的深度z值)来决定物体是否可见。如同10.21所示,同一条光线上有两个点P1和P2,由于P2的z值大于P1,所以P2在阴影中。
我们需要使用两对着色器以实现阴影:(1)、一对着色器用来计算光源到物体的距离,(2)、另一对着色器根据(1)中计算出的距离绘制场景。使用一张纹理图像把(1)的结果传入(2)中,这张纹理图像就被成为阴影贴图(shadow map),而通过阴影贴图实现阴影的方法就被称为阴影映射(shadow mapping)。阴影映射的过程包括以下两步:
1、 将视点移到光源的位置处,并运行(1)中的着色器。这时,那些“将要被绘处”的片元都是被光照射到的,即落在这个像素上的各个片元中最前面的。我们并不实际地绘制出片元的颜色,而是将片元的z值写入到阴影贴图中。
2、将视点移回原来的位置,运行(2)中的着色器绘制场景。此时,我们计算出每个片元在光源坐标系(即(1)中的视点坐标系)下的坐标,并与阴影贴图中记录的z值比较,如果前者大于后者,就说明当前片元处在阴影之中,用较深暗的颜色绘制。
示例程序:
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>WebGL绘制阴影</title>
</head>
<body "main()">
<canvas id="webgl" width="400" height="400">
Please use the browser supporting "canvas"
</canvas>
<script src="../lib/webgl-utils.js"></script>
<script src="../lib/webgl-debug.js"></script>
<script src="../lib/cuon-utils.js"></script>
<script src="../lib/cuon-matrix.js"></script>
<script>
//设置WebGL全屏显示
var canvas = document.getElementById("webgl");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
//设置阴影贴图顶点着色器
var shadowVertexShaderSource = "" +
"attribute vec4 a_Position;/n" +
"uniform mat4 u_MvpMatrix;/n" +
"void main(){/n" +
" gl_Position = u_MvpMatrix * a_Position;/n" + //计算出在灯源视点下各个坐标的位置
"}/n";
//设置阴影贴图的片元着色器
var shadowFragmentShaderSource = "" +
"#ifdef GL_ES/n" +
"precision mediump float;/n" +
"#endif/n" +
"void main(){/n" +
" gl_FragColor = vec4( 0.0, 0.0, 0.0,gl_FragCoord.z);/n" + //将灯源视点下的每个顶点的深度值存入绘制的颜色内
"}/n";
//正常绘制的顶点着色器
var vertexShaderSource = "" +
"attribute vec4 a_Position;/n" +
"attribute vec4 a_Color;/n" +
"uniform mat4 u_MvpMatrix;/n" + //顶点的模型投影矩阵
"uniform mat4 u_MvpMatrixFromLight;/n" + //顶点基于光源的模型投影矩阵
"varying vec4 v_PositionFromLight;/n" + //将基于光源的顶点位置传递给片元着色器
"varying vec4 v_Color;/n" + //将颜色传递给片元着色器
"void main(){/n" +
" gl_Position = u_MvpMatrix * a_Position;/n" + //计算并设置顶点的位置
" v_PositionFromLight = u_MvpMatrixFromLight * a_Position;/n" + //计算基于光源的顶点位置
" v_Color = a_Color;/n" +
"}/n";
//正常绘制的片元着色器
var fragmentShaderSource = "" +
"#ifdef GL_ES/n" +
"precision mediump float;/n" +
"#endif/n" +
"uniform sampler2D u_ShadowMap;/n" + //纹理的存储变量
"varying vec4 v_PositionFromLight;/n" + //从顶点着色器传过来的基于光源的顶点坐标
"varying vec4 v_Color;/n" + //顶点的颜色
"void main(){/n" +
" vec3 shadowCoord = (v_PositionFromLight.xyz/v_PositionFromLight.w)/2.0 + 0.5;/n" +
" vec4 rgbaDepth = texture2D(u_ShadowMap, shadowCoord.xy);/n" +
" float depth = rgbaDepth.a;/n" +
" float visibility = (shadowCoord.z > depth + 0.005) ? 0.5 : 1.0;/n" +
" gl_FragColor = vec4(v_Color.rgb * visibility, v_Color.a);/n" +
"}/n";
//生成的纹理的分辨率,纹理必须是标准的尺寸 256*256 1024*1024 2048*2048
var resolution = 256;
var offset_width = resolution;
var offset_height = resolution;
//灯光的位置
var light_x = 0.0;
var light_y = 7.0;
var light_z = 2.0;
function main() {
var canvas = document.getElementById("webgl");
var gl = getWebGLContext(canvas);
if(!gl){
console.log("无法获取WebGL的上下文");
return;
}
//初始化阴影着色器,并获得阴影程序对象,相关变量的存储位置
var shadowProgram = createProgram(gl, shadowVertexShaderSource, shadowFragmentShaderSource);
shadowProgram.a_Position = gl.getAttribLocation(shadowProgram, "a_Position");
shadowProgram.u_MvpMatrix = gl.getUniformLocation(shadowProgram, "u_MvpMatrix");
if(shadowProgram.a_Position < 0 || !shadowProgram.u_MvpMatrix ){
console.log("无法获取到阴影着色器的相关变量");
return;
}
//初始化正常绘制着色器,获取到程序对象并获取相关变量的存储位置
var normalProgram = createProgram(gl, vertexShaderSource, fragmentShaderSource);
normalProgram.a_Position = gl.getAttribLocation(normalProgram, "a_Position");
normalProgram.a_Color = gl.getAttribLocation(normalProgram, "a_Color");
normalProgram.u_MvpMatrix = gl.getUniformLocation(normalProgram, "u_MvpMatrix");
normalProgram.u_MvpMatrixFromLight = gl.getUniformLocation(normalProgram, "u_MvpMatrixFromLight");
normalProgram.u_ShadowMap = gl.getUniformLocation(normalProgram, "u_ShadowMap");
if(normalProgram.a_Position < 0 || normalProgram.a_Color < 0 || !normalProgram.u_MvpMatrix || !normalProgram.u_MvpMatrixFromLight || !normalProgram.u_ShadowMap){
console.log("无法获取到正常绘制着色器的相关变量");
return;
}
//设置相关数据,并存入缓冲区内
var triangle = initVertexBuffersForTriangle(gl);
var plane = initVertexBuffersForPlane(gl);
if(!triangle || !plane){
console.log("无法设置相关顶点的信息");
return;
}
//设置帧缓冲区对象
var fbo = initFramebufferObject(gl);
if(!fbo){
console.log("无法设置帧缓冲区对象");
return;
}
//开启0号纹理缓冲区并绑定帧缓冲区对象的纹理
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, fbo.texture);
//设置背景设并开启隐藏面消除功能
gl.clearColor(0.0,0.0,0.0,1.0);
gl.enable(gl.DEPTH_TEST);
//声明一个光源的变换矩阵
var viewProjectMatrixFromLight = new Matrix4();
viewProjectMatrixFromLight.setPerspective(70.0, offset_width/offset_height, 1.0, 100.0);
viewProjectMatrixFromLight.lookAt(light_x, light_y, light_z,0.0,0.0,0.0,0.0,1.0,0.0);
//为常规绘图准备视图投影矩阵
var viewProjectMatrix = new Matrix4();
viewProjectMatrix.setPerspective(45.0, canvas.width/canvas.height, 1.0, 100.0);
viewProjectMatrix.lookAt(0.0,7.0,9.0,0.0,0.0,0.0,0.0,1.0,0.0);
var currentAngle = 0.0; //声明当前旋转角度的变量
var mvpMatrixFromLight_t = new Matrix4(); //光源(三角形)的模型投影矩阵
var mvpMatrixFromLight_p = new Matrix4(); //光源(平面)的模型投影矩阵
(function tick() {
currentAngle = animate(currentAngle);
//切换绘制场景为帧缓冲区
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.viewport(0.0,0.0,offset_height,offset_height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.useProgram(shadowProgram); //使用阴影程序对象绘制阴影纹理
//绘制三角形和平面(用于生成阴影贴图)
drawTriangle(gl, shadowProgram, triangle, currentAngle, viewProjectMatrixFromLight);
mvpMatrixFromLight_t.set(g_mvpMatrix); //稍后使用
drawPlane(gl, shadowProgram, plane, viewProjectMatrixFromLight);
mvpMatrixFromLight_p.set(g_mvpMatrix); //稍后使用
//解除帧缓冲区的绑定,绘制正常颜色缓冲区
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0.0, 0.0, canvas.width, canvas.height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
//切换为正常的程序对象并绘制
gl.useProgram(normalProgram);
gl.uniform1i(normalProgram.u_ShadowMap, 0.0);
//绘制三角形和平面(正常绘制的图形)
gl.uniformMatrix4fv(normalProgram.u_MvpMatrixFromLight, false, mvpMatrixFromLight_t.elements);
drawTriangle(gl, normalProgram, triangle, currentAngle, viewProjectMatrix);
gl.uniformMatrix4fv(normalProgram.u_MvpMatrixFromLight, false, mvpMatrixFromLight_p.elements);
drawPlane(gl, normalProgram, plane, viewProjectMatrix);
requestAnimationFrame(tick);
})();
}
//声明坐标转换矩阵
var g_modelMatrix = new Matrix4();
var g_mvpMatrix = new Matrix4();
function drawTriangle(gl,program,triangle,angle,viewProjectMatrix) {
//设置三角形图形的旋转角度,并绘制图形
g_modelMatrix.setRotate(angle, 0.0, 1.0, 0.0);
draw(gl, program, triangle, viewProjectMatrix);
}
function drawPlane(gl, program, plane, viewProjectMatrix) {
//设置平面图形的旋转角度并绘制
g_modelMatrix.setRotate(-45.0, 0.0, 1.0, 1.0);
draw(gl, program, plane, viewProjectMatrix);
}
function draw(gl, program, obj, viewProjectMatrix) {
initAttributeVariable(gl, program.a_Position, obj.vertexBuffer);
//判断程序对象上面是否设置了a_Color值,如果有,就设置颜色缓冲区
if(program.a_Color != undefined){
initAttributeVariable(gl, program.a_Color, obj.colorBuffer);
}
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, obj.indexBuffer);
//设置模板视图投影矩阵,并赋值给u_MvpMatrix
g_mvpMatrix.set(viewProjectMatrix);
g_mvpMatrix.multiply(g_modelMatrix);
gl.uniformMatrix4fv(program.u_MvpMatrix, false, g_mvpMatrix.elements);
gl.drawElements(gl.TRIANGLES, obj.numIndices, gl.UNSIGNED_BYTE, 0);
}
function initAttributeVariable(gl, a_attribute, buffer) {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(a_attribute, buffer.num, buffer.type, false, 0, 0);
gl.enableVertexAttribArray(a_attribute);
}
var angle_step = 30;
var last = +new Date();
function animate(angle) {
var now = +new Date();
var elapsed = now - last;
last = now;
var newAngle = angle + (angle_step*elapsed)/1000.0;
return newAngle%360;
}
function initFramebufferObject(gl) {
var framebuffer, texture, depthBuffer;
//定义错误函数
function error() {
if(framebuffer) gl.deleteFramebuffer(framebuffer);
if(texture) gl.deleteFramebuffer(texture);
if(depthBuffer) gl.deleteFramebuffer(depthBuffer);
return null;
}
//创建帧缓冲区对象
framebuffer = gl.createFramebuffer();
if(!framebuffer){
console.log("无法创建帧缓冲区对象");
return error();
}
//创建纹理对象并设置其尺寸和参数
texture = gl.createTexture();
if(!texture){
console.log("无法创建纹理对象");
return error();
}
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, offset_width, offset_height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
framebuffer.texture = texture;//将纹理对象存入framebuffer
//创建渲染缓冲区对象并设置其尺寸和参数
depthBuffer = gl.createRenderbuffer();
if(!depthBuffer){
console.log("无法创建渲染缓冲区对象");
return error();
}
gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, offset_width, offset_height);
//将纹理和渲染缓冲区对象关联到帧缓冲区对象上
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER,depthBuffer);
//检查帧缓冲区对象是否被正确设置
var e = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if(gl.FRAMEBUFFER_COMPLETE !== e){
console.log("渲染缓冲区设置错误"+e.toString());
return error();
}
//取消当前的focus对象
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.bindTexture(gl.TEXTURE_2D, null);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
return framebuffer;
}
function initVertexBuffersForPlane(gl) {
// 创建一个面
// v1------v0
// | |
// | |
// | |
// v2------v3
// 顶点的坐标
var vertices = new Float32Array([
3.0, -1.7, 2.5, -3.0, -1.7, 2.5, -3.0, -1.7, -2.5, 3.0, -1.7, -2.5 // v0-v1-v2-v3
]);
// 颜色的坐标
var colors = new Float32Array([
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0
]);
// 顶点的索引
var indices = new Uint8Array([0, 1, 2, 0, 2, 3]);
//将顶点的信息写入缓冲区对象
var obj = {};
obj.vertexBuffer = initArrayBufferForLaterUse(gl, vertices, 3, gl.FLOAT);
obj.colorBuffer = initArrayBufferForLaterUse(gl, colors, 3, gl.FLOAT);
obj.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE);
if(!obj.vertexBuffer || !obj.colorBuffer || !obj.indexBuffer) return null;
obj.numIndices = indices.length;
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
return obj;
}
function initVertexBuffersForTriangle(gl) {
// Create a triangle
// v2
// / |
// / |
// / |
// v0----v1
// 顶点的坐标
var vertices = new Float32Array([-0.8, 3.5, 0.0, 0.8, 3.5, 0.0, 0.0, 3.5, 1.8]);
// 颜色的坐标
var colors = new Float32Array([1.0, 0.5, 0.0, 1.0, 0.5, 0.0, 1.0, 0.0, 0.0]);
// 顶点的索引
var indices = new Uint8Array([0, 1, 2]);
//创建一个对象保存数据
var obj = {};
//将顶点信息写入缓冲区对象
obj.vertexBuffer = initArrayBufferForLaterUse(gl, vertices, 3, gl.FLOAT);
obj.colorBuffer = initArrayBufferForLaterUse(gl, colors, 3, gl.FLOAT);
obj.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE);
if(!obj.vertexBuffer || !obj.colorBuffer || !obj.indexBuffer) return null;
obj.numIndices = indices.length;
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
return obj;
}
function initArrayBufferForLaterUse(gl, data, num, type) {
var buffer = gl.createBuffer();
if(!buffer){
console.log("无法创建缓冲区对象");
return null;
}
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
buffer.num = num;
buffer.type = type;
return buffer;
}
function initElementArrayBufferForLaterUse(gl, data, type) {
var buffer = gl.createBuffer();
if(!buffer){
console.log("无法创建着色器");
return null;
}
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, gl.STATIC_DRAW);
buffer.type = type;
return buffer;
}
</script>
</body>
</html>
来源:https://blog.csdn.net/wangcuiling_123/article/details/86308805
WEBGL学习网(WebGLStudy.COM)专注提供WebGL 、ThreeJS、BabylonJS等WEB3D开发案例源码下载。
声明信息:
1. 本站部分资源来源于用户上传和网络,如有侵权请邮件联系站长:1218436398@qq.com!我们将尽快处理。
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源打赏售价用于赞助本站提供的服务支出(包括不限服务器、网络带宽等费用支出)!
7.欢迎加QQ群学习交流:549297468 ,或者搜索微信公众号:WebGL学习网
WEBGL学习网 » 原生WebGL绘制阴影技术
WEBGL学习网 » 原生WebGL绘制阴影技术