当前位置: 首页 > news >正文

Threejs中的Shadow Mapping(阴影贴图)

简而言之,步骤如下:

1.从灯光位置视点(阴影相机)创建深度图。

2.从相机的位置角度进行屏幕渲染,在每个像素点,比较由阴影相机的MVP矩阵计算的深度值和深度图的值的大小,如果深度图值小的话,则表示该像素点有阴影,就在此处渲染阴影状态。

演示效果

1.创建深度图

基本上,从头开始做阴影贴图时,只需要准备三样东西:灯光位置、阴影相机和深度图。为了更方便理解,本文用ShadowMapViewer来将深度图进行可视化。

方向光(Directional Light)

首先创造一个光源。

const light = new THREE.DirectionalLight( 0xffffff, 1.0 );
light.position.set(-30, 40, 10);
scene.add(light);

DirectionalLight有一个shadow参数,因此附加一个从灯光位置观察的阴影相机和一个从阴影相机角度写入深度值的fbo(frame buffer object,帧缓存对象)。

阴影相机(Shadow Camera)

由于光线是定向的,因此使用OrthographicCamera(正视相机)作为阴影相机来创建平行投影的深度图。

最重要的是必须设置好相机范围(视锥体),如果阴影相机范围太宽,深度图会不准确,因此最好将其设置在尽可能渲染阴影的最小范围,不要太宽或太窄。

const frustumSize = 80;light.shadow.camera = new THREE.OrthographicCamera(-frustumSize / 2,frustumSize / 2,frustumSize / 2,-frustumSize / 2,1,80
);// 和灯光位置保持一致
light.shadow.camera.position.copy(light.position);
light.shadow.camera.lookAt(scene.position);
scene.add(light.shadow.camera);

深度图(Depth Map)

接下来,为阴影相机视点准备深度图。

深度图如果分辨率设置太低图像会很粗糙,所以这次我们将准备一个 2048 x 2048 的fbo。

一般为了尽可能以高精度写入深度值,通常使用16位或32位纹理,但由于WebGL尚不兼容尚不支持浮动纹理的设备,所以我们用8位纹理的所有四个通道来存储单个32位值(在本例中为深度值),我们将使用three.js的ShaderChunk,方便转换。

light.shadow.mapSize.x = 2048;
light.shadow.mapSize.y = 2048;const pars = { minFilter: THREE.NearestFilter,magFilter: THREE.NearestFilter, format: THREE.RGBAFormat
};light.shadow.map = new THREE.WebGLRenderTarget( light.shadow.mapSize.x, this.light.shadow.mapSize.y, pars );

用于渲染深度图的材质

const shadowMaterial = new THREE.ShaderMaterial({vertexShader: vertexShader,fragmentShader: shadowFragmentShader
});

顶点shader基本是一样的。

void main(){gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
}

因为我们要写入的深度图是8bit纹理,但是要输入的数据是32bit。在这里可以用three.js的shaderChunk中使用packDepthToRGBA来存储使用rgba通道的深度值。

// https://github.com/mrdoob/three.js/blob/master/src/renderers/shaders/ShaderChunk/packing.glsl.js#L18
#include <packing>void main(){// gl_FragCoord.z contains depth values from 0 to 1 in the viewing frustum range of the shadow camera.// 0 for near clip, 1 for far clipgl_FragColor = packDepthToRGBA(gl_FragCoord.z);
}

写入深度值

shadowMaterial赋到mesh上,并渲染为深度图。

由于需要为阴影相机视点创建深度图,因此将深度图指定为“renderTarget”,将shadowCamera指定为“camera”。

// 更新每一帧
mesh.material = shadowMaterial;
renderer.setRenderTarget(light.shadow.map);
renderer.render(scene, light.shadow.camera);

这样的话我们就渲染了深度图,然后用我刚才说的ShadowMapViewer来查看深度图的调试效果。

// https://threejs.org/examples/?q=shadow#webgl_shadowmap_viewerconst depthViewer = new ShadowMapViewer(light);
depthViewer.size.set( 300, 300 );...
// render to canvas
renderer.setRenderTarget(null);
depthViewer.render( renderer );

越靠近阴影相机的地方,深度值越小(因为ShadowMapViewer对结果取反了,所以越是白的地方,深度值越小)。

2.比较深度并创建阴影

屏幕渲染材质

将光照位置和深度图放入uniform变量中,阴影相机投影矩阵和视图矩阵也放入uniform变量中,因为在阴影相机的 MVP矩阵中计算的深度也必须在这个着色器中计算并与深度图进行比较。

const uniforms = {uColor: {value: new THREE.Color(color)},uLightPos: {value: light.position},uDepthMap: {value: light.shadow.map.texture},uShadowCameraP: {value: light.shadow.camera.projectionMatrix},uShadowCameraV: {value: light.shadow.camera.matrixWorldInverse},
}
const material = new THREE.ShaderMaterial({vertexShader,fragmentShader,uniforms,
});

在顶点着色器中添加一些代码。

uniform mat4 uShadowCameraP;
uniform mat4 uShadowCameraV;varying vec4 vShadowCoord;varying vec3 vNormal;void main(){vNormal = normal;vec3 pos = position;gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(pos, 1.0);// 阴影相机视点的坐标传递给片段着色器并与深度图进行比较vShadowCoord = uShadowCameraP * uShadowCameraV * modelMatrix * vec4(pos, 1.0);
}

vShadowCoord的结果是裁剪空间中的坐标,因此vShadowCoord.xyz / vShadowCoord.w 的范围从 (-1, -1, -1)到(1,1,1)。

vShadowCoord.z / vShadowCoord.w是深度值,所以让它在0和1之间转换并与深度图进行比较。并让vShadowCoord.xy / vShadowCoord.w在(0,0)和(1,1)之间转换为uv来参考深度图。之所以使用MVP矩阵计算得到的结果作为uv,是因为我们可以参考与生成深度图的像素相同点的深度值。

由于深度图值是较早通过在rgba中分配32位数据输入的,因此在引用时需要将其恢复为原始值。

此解码使用来自three.js中相同ShaderChunk的unpackRGBAToDepth

uniform vec3 uColor;
uniform sampler2D uDepthMap;
uniform vec3 uLightPos;varying vec3 vNormal;
varying vec4 vShadowCoord;// https://github.com/mrdoob/three.js/blob/master/src/renderers/shaders/ShaderChunk/packing.glsl.js#L24
#include <packing>void main(){vec3 shadowCoord = vShadowCoord.xyz / vShadowCoord.w * 0.5 + 0.5;float depth_shadowCoord = shadowCoord.z;vec2 depthMapUv = shadowCoord.xy;float depth_depthMap = unpackRGBAToDepth(texture2D(uDepthMap, depthMapUv));// Compare and if the depth value is smaller than the value in the depth map, then there is an occluder and the shadow is drawn.float shadowFactor = step(depth_shadowCoord, depth_depthMap);// check the result of the shadow factor.gl_fragColor = vec4(vec3(shadowFactor), 1.0);
}

在循环函数中,将屏幕渲染过程放在深度图渲染之后。

// 在循环函数中写入深度图
mesh.material = shaderMaterial;
renderer.setRenderTarget(light.shadow.map);
renderer.render(scene, light.shadow.camera);// 放置一个用于屏幕渲染的材质并将其渲染到画布上。
mesh.material = material;
renderer.setRenderTarget(null);
renderer.render(scene, camera);

调整深度值比较

当显示shadowFactor(比较深度值的结果)时,会生成阴影,但会显示出一些奇怪类似摩尔纹的图案,这种现象被称为shadow acne,必须通过减去这一点的bias来比较深度值。

void main(){...float cosTheta = dot(normalize(uLightPos), vNormal);float bias = 0.005 * tan(acos(cosTheta)); // cosTheta is dot( n,l ), clamped between 0 and 1bias = clamp(bias, 0.0, 0.01);float shadowFactor = step(depth_shadowCoord - bias, depth_depthMap);gl_fragColor = vec4(vec3(shadowFactor), 1.0);
}

乘以定向光,然后完成着色。

http://www.lryc.cn/news/1547.html

相关文章:

  • 本质安全设备标准(IEC60079-11)的理解(四)
  • (record)QEMU安装最小linux系统——TinyCore(命令行版)
  • C++中的cast类型转换
  • 西瓜数据集读取的详细解决方案
  • Mac开发环境配置
  • 概率论面试题1:玫瑰花
  • 【DGL】图分类
  • 时间复杂度的计算(2023-02-10)
  • 测试开发之Django实战示例 第六章 追踪用户行为
  • 红米9a手动root方法
  • Open3D 点云最小二乘法拟合平面(剔除噪声,Python版本)
  • 【SpringBoot】简述springboot项目启动数据加载内存中的三种方法
  • 【一文速通】各种机器学习算法的特点及应用场景
  • 多传感器融合定位十四-基于图优化的定位方法
  • PHP基于TCPDF第三方类生成PDF文件
  • SpringCloud(19):Sentinel定义资源的方式
  • 前端 ES6 之 Promise 实践应用与控制反转
  • LightGBM
  • Science:北京脑研究中心李莹实验室揭示性满足感的分子机制
  • Element UI框架学习篇(三)
  • 尚硅谷的尚融宝项目
  • 12 Day:内存管理
  • linux基本功系列之lsof命令实战
  • 基础篇:02-SpringCloud概述
  • 【软件测试】软件测试工作上95%会遇到的问题,你遇到多少?
  • 4.5.4 LinkedList
  • Python之FileNotFoundError: [Errno 2] No such file or directory问题处理
  • C语言中耳熟能详的printf与scanf
  • 【数据结构】复杂度讲解
  • JAVA-线程池技术