Three.js阴影贴图

生成阴影贴图的步骤如下:

  • 从光位置视点(阴影相机)创建深度图。
  • 从相机的角度进行屏幕渲染
  • 在每个像素点,将阴影相机的MVP矩阵计算出的深度值与深度图值进行比较
  • 如果深度图值较低,则说明该像素点存在阴影 ,因此渲染阴影。

1、创建深度贴图

基本上,当从头开始做阴影贴图时,你只需要准备三件事:灯光位置、阴影相机和深度贴图,但由于我认为使用可以可视化深度图的ShadowMapViewer更容易理解,所以我们将准备灯光 以及在 Three.js 中以常规方式添加阴影。

1.1 定向光

首先,制作灯光。

const light = new THREE.DirectionalLight( 0xffffff, 1.0 );
// The light is directed from the light's position to the origin of the world coordinates.
light.position.set(-30, 40, 10);scene.add(light);

DirectionalLight 有一个阴影参数,因此请附加一个从光源位置查看的阴影相机和一个从阴影相机角度写入深度值的 fbo。

1.2 阴影相机

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

重要的是,必须设置相机范围(视锥体),以便完全包含要阴影的所有对象。
如果阴影相机范围太宽,深度图将不准确,因此最好将其设置在尽可能渲染阴影的最低范围,不要太宽或太窄,以创建漂亮的阴影。

const frustumSize = 80;light.shadow.camera = new THREE.OrthographicCamera(-frustumSize / 2,frustumSize / 2,frustumSize / 2,-frustumSize / 2,1,80
);// Same position as LIGHT position.
light.shadow.camera.position.copy(light.position);
light.shadow.camera.lookAt(scene.position);
scene.add(light.shadow.camera);

1.3 深度贴图

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

低分辨率会使图像变得粗糙,因此我们这次准备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 );

1.4 用于渲染深度图的材质

准备在深度图上写入时要使用的材质。

基本上顶点是一样的,深度值是在片段着色器中输入的。

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

顶点着色器:

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

要写入的深度贴图是8位纹理,但我们要输入的数据是32位。 我们在two.js的shaderChunk中使用packDepthToRGBA来存储使用rgba通道的深度值。

gl_FragCoord.z 包含从 0 到 1 的深度值,因此请按原样输入这些值。

片元着色器:

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

1.5 写入深度值

将 ShadowMaterial 设置为网格并渲染为深度图。

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

// update every frame
mesh.material = shadowMaterial;
renderer.setRenderTarget(light.shadow.map);
renderer.render(scene, light.shadow.camera);

现在我们有了深度图渲染。准备一个阴影图查看器来查看深度图的外观以进行调试。

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

距离阴影相机越近,深度值越小,因此结果本质上是相反的,但它是检查深度图是否正确渲染的好工具。

2、比较深度并创建阴影

创建深度图后,就可以进行屏幕渲染了。

2.1 屏幕渲染材质

准备用于屏幕渲染的材质。

灯光位置和深度图被放入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);// Coordinates from the shadow camera viewpoint// Pass to fragment shader and compare with depth map.vShadowCoord = uShadowCameraP * uShadowCameraV * modelMatrix * vec4(pos, 1.0);
}

vShadowCoord 的结果是剪辑空间中的坐标系,因此 vShadowCoord.xyz / vShadowCoord.w 的范围从 (-1, -1, -1) 到 (1,1,1)。如果你想了解更多关于 MVP 矩阵的信息,可以点击这里。

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

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

// in the loop function
// Writing into the depth map
mesh.material = shaderMaterial;
renderer.setRenderTarget(light.shadow.map);
renderer.render(scene, light.shadow.camera);// put a material for screen rendering and render it to canvas.
mesh.material = material;
renderer.setRenderTarget(null);
renderer.render(scene, camera);

2.2 调整深度值比较

当显示shadowFactor(比较深度值的结果)时,会生成阴影,但会创建额外的图案。这称为阴影痘痘(shadow acne),必须通过减去考虑到这一点的偏差来比较深度值。片元着色器如下:

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

痘痘消失了。

阴影区域不干净,但定向光过程可以将其覆盖,所以我将保持原样。影子相机视锥体的外侧被切掉了,所以我只处理那部分。片元着色器代码如下:

void main(){// Assume no shadow except for viewing frustum.// https://github.com/mrdoob/three.js/blob/master/src/renderers/shaders/ShaderChunk/packing.glsl.js#L24bvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );bool inFrustum = all( inFrustumVec );bvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );bool frustumTest = all( frustumTestVec );if(frustumTest == false){shadowFactor = 1.0;}float difLight = max(0.0, cosTheta);float shading = shadowFactor * difLight;gl_fragColor = vec4(vec3(shading), 1.0);
}

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

应用于基于着色的 uColor。片元着色器如下:

void main(){color = mix(uColor - 0.1, uColor + 0.1, shading);gl_fragColor = vec4(color, 1.0);
}

3、额外的方法 - 剔除正面

我还将向拟展示如何无偏差地绘制阴影。

渲染深度图时,仅渲染背面网格,而在屏幕渲染期间渲染正面网格。

这种方法不需要拉偏,因为痘痘不会出现。但是,只能使用闭合网格,因此如果放置平面,则不会创建其阴影。在此演示中,此方法有效,因为只有闭合网格。

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

片元着色器:

void main(){vec3 shadowCoord = (vShadowCoord.xyz / vShadowCoord.w * 0.5 + 0.5);float depth_shadowCoord = shadowCoord.z;float depth_depthMap = unpackRGBAToDepth(texture2D(uDepthMap, shadowCoord.xy));// Acne does not arise, so no bias is required.float shadowFactor = step(depth_shadowCoord, depth_depthMap);...
}

这是将正确的对象从盒子替换为平面的比较。

对于其他封闭物体没有区别,但是对于平面来说,剔除正面的方法对于阴影不起作用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/587687.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络-HTTP相关知识-HTTPS基础

HTTPS与HTTP的区别 在TCP和HTTP网络层之间加入了SSL/TLS安全协议。HTTPS在TCP三次握手之后&#xff0c;还需进行SSL/TLS的握手过程。HTTP默认端口号是80&#xff0c;HTTPS默认端口号是443。 HTTPS解决的风险 HTTPS主要解决了以下三种风险&#xff1a; 窃听风险&#xff1a;H…

极简7照训练法,奇趣相机引领儿童AI摄影潮流

近日&#xff0c;奇趣未来推出一款专注于儿童AI摄影市场的微信小程序——奇趣相机&#xff0c;搭载了专为中国儿童精心研发的AIGC大模型&#xff0c;精准捕捉并贴合亚洲儿童人脸特征&#xff0c;让每一个孩子的笑容都能被完美定格。它不仅涵盖了从3岁至12岁各个年龄段的儿童摄影…

Spring中BeanFactoryPostProcessor详解

目录 功能与作用 使用案例 spring提供的常见BeanFactoryPostProcessor 1.EventListenerMethodProcessor 2.BeanDefinitionRegistryPostProcessor 功能与作用 使用案例 spring提供的唯一BeanDefinitionRegistryPostProcessor 总结 功能与作用 参考BeanFactoryPostProce…

网络安全应急响应:保护网络安全的最后一道防线

网络安全应急响应&#xff1a;保护网络安全的最后一道防线 网络安全是当今信息社会中至关重要的问题&#xff0c;网络攻击的频繁发生使得企业、政府和个人面临着越来越大的安全威胁。为了及时有效地应对网络安全事件&#xff0c;网络安全应急响应成为了必不可少的一环。 小德将…

Mamba解读(FlashAttention,SSM,LSSL,S4,S5,Mamba)

Sequence modelScale and EfficiencyFlashAttentionMotivationMethodFlashDecoding MambaState-Space Models&#xff08;SSM&#xff09;Selective State Space Models&#xff08;Mamba&#xff09; Sequence model seq2seq任务将 输入序列 x ( t ) x(t) x(t) 映射为 输出序…

Web3 革命:揭示区块链技术的全新应用

随着数字化时代的不断发展&#xff0c;区块链技术作为一项颠覆性的创新正在改变着我们的世界。而在这一技术的进步中&#xff0c;Web3正逐渐崭露头角&#xff0c;为区块链技术的应用带来了全新的可能性。本文将探讨Web3革命所揭示的区块链技术全新应用&#xff0c;并展望其未来…

Linux网络编程一(协议、TCP协议、UDP、socket编程、TCP服务器端及客户端)

文章目录 协议1、分层模型结构2、网络应用程序设计模式3、ARP协议4、IP协议5、UDP协议6、TCP协议 Socket编程1、网络套接字(socket)2、网络字节序3、IP地址转换4、一系列函数5、TCP通信流程分析 第二次更新&#xff0c;自己再重新梳理一遍… 协议 协议&#xff1a;指一组规则&…

图像处理与视觉感知---期末复习重点(6)

文章目录 一、图像分割二、间断检测2.1 概述2.2 点检测2.3 线检测2.4 边缘检测 三、边缘连接3.1 概述3.2 Hough变换3.3 例子3.4 Hough变换的具体步骤3.5 Hough变换的法线表示形式3.6 Hough变换的扩展 四、阈值处理4.1 概述4.2 计算基本全局阈值算法4.3 自适应阈值 五、基于区域…

视频汇聚/安防监控/视频存储EasyCVR平台EasyPlayer播放器更新:新增【性能面板】

视频汇聚/安防监控/视频存储平台EasyCVR基于云边端架构&#xff0c;可以在复杂的网络环境中快速、灵活部署&#xff0c;平台视频能力丰富&#xff0c;可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云…

什么是js、ajax

1.什么是js JavaScript&#xff08;简称 JS&#xff09;是一种轻量级、解释型的编程语言&#xff0c;通常用于在 Web 页面上添加交互性、动态性和动画效果。它是世界上最流行的编程语言之一&#xff0c;也是唯一一种可以在 Web 浏览器中运行的编程语言。 2.什么是AJAX ajax…

Spring Boot接收从前端传过来的数据常用方式以及处理的技巧

一、params 传参 参数是会拼接到url后面的请求 场景规范:url后面的key值<=3个参数的时候,使用params 传参 支持的请求方式:get(正规的是get方式)、post 都行 例如: http://localhost:8080/simpleParam?name=Tom&age=10 在postman里面的体现为 后端接收的接口…

Set a Light 3D Studio:探索光影艺术的全新维度mac/win中文版

Set a Light 3D Studio 是一款领先的三维建模和渲染软件&#xff0c;它将设计师、艺术家和摄影师的创意想法转化为生动逼真的三维场景。这款软件以其强大的功能和直观的界面&#xff0c;成为行业内众多专业人士的首 选工具。 set.a.light 3D STUDIO中文版软件获取 在Set a Lig…