简而言之,步骤如下:

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);
}

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

Threejs中的Shadow Mapping(阴影贴图)相关推荐

  1. OpenGL shadow mapping 阴影贴图的实例

    OpenGL shadow mapping 阴影贴图 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <glad/glad.h> #i ...

  2. OpenGL Shadow Mapping阴影贴图的实例

    OpenGL Shadow Mapping阴影贴图 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include "vapp.h" #inc ...

  3. OpenGL Shadow Mapping阴影贴图的实例

    OpenGL 阴影贴图 先上图,再解答. 正常显示 按下2键 按下3键 完整主要的源代码 源代码剖析 先上图,再解答. 正常显示 按下2键 按下3键

  4. 【Shading】Shadow Mapping 阴影映射

    课程来源:GAMES101-现代计算机图形学入门-闫令琪 Lecture12 GAMES101 现代计算机图形学入门 主讲老师:闫令琪,UCSB 课程主页:https://sites.cs.ucsb. ...

  5. Shadow Map阴影贴图技术之探 【转】

    这两天勉勉强强把一个shadowmap的demo做出来了.参考资料多,苦头可不少.Shadow Map技术是目前与Shadow Volume技术并行的传统阴影渲染技术,而且在游戏领域可谓占很大优势.本 ...

  6. 阴影贴图(Shadow mapping)

    安卓demo 下载 转载请声明出处:http://blog.csdn.NET/xiaoge132/article/details/51458489 阴影贴图(Shadow mapping) 是在三维计 ...

  7. [转载] [OpenGL] shadow mapping(实时阴影映射)

    参考链接: Java中静态函数的阴影(方法隐藏) 转载原创:ZJU_fish1996   http://blog.csdn.net/zju_fish1996/article/details/51932 ...

  8. 【GAMES-202实时渲染】1、软阴影01(Shadow Mapping、Peter Panning、PCSS原理超详细)

    Lecture3 Real-Time shadows1 1 Shadow Mapping回顾 2 Shadow Mapping缺点及解决方案 2.1 自遮挡现象 解决方案1 定义一个bias 解决方案 ...

  9. [OpenGL] shadow mapping(实时阴影映射)

    source:原文地址 code:点击可以直接下载源代码 1978年,Lance Williams在其发表的论文<Casting curved shadows on curved surface ...

最新文章

  1. matlab振动频谱分析是不是要,VB和Matlab混编实现振动信号的频谱分析
  2. layDate的使用
  3. 静态html文件js读取url参数
  4. oracle中有类似split的方法么,ORACLE中字符串split的一种方法
  5. char[]和char*的区别(转)
  6. mac中apache服务器及虚拟主机配置
  7. cacti 忘记密码的方法
  8. 深度学习图像融合_基于深度学习的图像超分辨率最新进展与趋势【附PDF】
  9. Java笔记-JNI的基本使用(Java调用C++的dll)
  10. 摩拜单车又涨价了!真的要骑不起了
  11. android应用程序列表,List列表应用程序-小知识 #103
  12. 2017百度之星复赛:1006. Valley Numer(数位DP)
  13. 计算机信息安全技术 学习笔记
  14. 死链检测工具Xenu的操作及使用方法
  15. 软件设计师考试都考什么内容
  16. php调用API支付接口 可个人使用,无需营业执照(使用第三方接口,调用的天工接口。)...
  17. 2021小白Python入门学习记录Day3(win10系统、Python3.9、使用Pycharm)python高级数据类型(字符串、列表、元组、字典、集合) 及其操作
  18. 三维主成分图matlab,PCA主成分分析之三维演示(Matlab)
  19. 怎样使用ApowerMirror实现将手机屏幕投屏到电脑
  20. struts的框架介绍

热门文章

  1. 高一学生计算机知识现状分析,高中信息技术学情分析
  2. 《LoadRunner 12七天速成宝典》—第2章2.5节参数和变量
  3. Spring Boot 学习第一步(搭建初步环境)
  4. 【总结】一些网络流量统计的网站及工具
  5. 不小心格式化交换机Flash后的挽救
  6. WEB网站类型系统中使用的OFFICE控件
  7. 访完美世界副总裁佟庆:游戏开发如何运用人工智能技术?
  8. 【雷达与对抗】【2012.12】基于脉冲多普勒雷达的行走检测
  9. 【电赛合集】19电磁炮.zip、17板球.zip、15风力摆.zip、13倒立摆.zip、(1994-2021)全国大学生电子设计竞赛历年真题.zip
  10. c九宫重排_关于全局择优搜索——重排九宫程序的一些问题