Three.js阴影贴图

news2024/12/26 22:59:02

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

  • 从光位置视点(阴影相机)创建深度图。
  • 从相机的角度进行屏幕渲染
  • 在每个像素点,将阴影相机的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 clip
    gl_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_viewer

const 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 1
    bias = 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#L24
    bvec4 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.coloradmin.cn/o/1564728.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

杰理芯片AC79——物联网远程点亮/关闭LED灯

杰理芯片的封装简直太香了&#xff08;比STM32香多了&#xff09;&#xff0c;SDK也封装得很好&#xff0c;对于我这种手残党简直不要太友好。赶紧学起来&#xff0c;快速实现你想要的功能吧&#xff01; 芯片选型 杰理AC79 资料文档 环境搭建以及点亮第一盏灯请访问&#x…

http: server gave HTTP response to HTTPS client 分析一下这个问题如何解决中文告诉我详细的解决方案

这个错误信息表明 Docker 客户端在尝试通过 HTTPS 协议连接到 Docker 仓库时&#xff0c;但是服务器却返回了一个 HTTP 响应。这通常意味着 Docker 仓库没有正确配置为使用 HTTPS&#xff0c;或者客户端没有正确配置以信任仓库的 SSL 证书。以下是几种可能的解决方案&#xff1…

VSCode好用插件

由于现在还是使用vue2&#xff0c;所以本文只记录vue2开发中好用的插件。 美化类插件不介绍了&#xff0c;那些貌似对生产力起不到什么大的帮助&#xff0c;纯粹的“唯心主义”罢了&#xff0c;但是如果你有兴趣的话可以查看上一篇博客&#xff1a;VSCode美化 1. vuter 简介&…

Apache Paimon实时数据糊介绍

Apache Paimon 是一种湖格式,可以使用 Flink 和 Spark 构建实时 数据糊 架构,用于流式和批处理操作。Paimon 创新地将湖格式和 LSM(日志结构合并树)结构相结合,将实时流式更新引入湖架构中。 Paimon 提供以下核心功能: 实时更新: 主键表支持大规模更新的写入,具有非常…

day02-SpringCloud02(Nacos、Feign、Gateway)

1.Nacos 配置管理 Nacos 除了可以做注册中心&#xff0c;同样可以做配置管理来使用。 1.1.统一配置管理 当微服务部署的实例越来越多&#xff0c;达到数十、数百时&#xff0c;逐个修改微服务配置就会让人抓狂&#xff0c;而且很容易出错。我们需要一种统一配置管理方案&#x…

REST API的指纹验证机制

前端或者客户端涉及数据相关的请求都是不安全的&#xff0c;从某种意义上只能通过一些手段降低请求不被容易使用。本来来介绍一种基于 JWT 的指纹机制。 关于 JWT 令牌机制就不详细介绍了。在 JWT 令牌中包含系统 JWT 指纹可以带来安全改进&#xff0c;而不会给用户带来任何不…

GitOps - 为 OpenShift GitOps 配置邮件通知

《OpenShift 4.x HOL教程汇总》 说明&#xff1a;本文已经 在OpenShift 4.15 OpenShift GitOps 1.11.2 环境中验证 文章目录 ArgoCD 的 Notification 功能简介启动 OpenShift GitOps 的 Notification 功能配置邮件通知验证参考 说明&#xff1a;先根据《OpenShift 4 之 GitOp…

基于深度学习的危险物品检测系统(网页版+YOLOv8/v7/v6/v5代码+训练数据集)

摘要&#xff1a;本文详细介绍基于YOLOv8/v7/v6/v5的危险物品检测技术。主要采用YOLOv8技术并整合了YOLOv7、YOLOv6、YOLOv5的算法&#xff0c;进行了细致的性能指标对比分析。博客详细介绍了国内外在危险物品检测方面的研究现状、数据集处理方法、算法原理、模型构建与训练代码…

人工智能|深度学习——基于Xception算法模型实现一个图像分类识别系统

一、Xception简介 在计算机视觉领域&#xff0c;图像识别是一个非常重要的任务&#xff0c;其应用涵盖了人脸识别、物体检测、场景理解等众多领域。随着深度学习技术的发展&#xff0c;深度卷积神经网络&#xff08;Convolutional Neural Networks&#xff0c;简称CNN&#xff…

激发创新活力:算力券与模型券,科技企业的新动力

激发创新活力&#xff1a;算力券与模型券&#xff0c;科技企业的新动力 在数字化转型的大潮中&#xff0c;科技创新已成为推动企业发展的核心动力。为了进一步激发企业的创新活力&#xff0c;政府和相关机构开始探索一种新的激励机制——发放“算力券”和“模型券”。这些创新…

面向对象编程(二)

面向对象&#xff08;二&#xff09; 类和对象的概念 类是一种抽象的数据类型&#xff0c;它是对某一类事物整体描述/定义&#xff0c;但是不能代表某一个具体的事物&#xff08;动物&#xff0c;植物&#xff0c;手机…&#xff09;对象是抽象概念的具体实例&#xff08;比如…

Prometheus+grafana环境搭建redis(docker+二进制两种方式安装)(四)

由于所有组件写一篇幅过长&#xff0c;所以每个组件分一篇方便查看&#xff0c;前三篇 Prometheusgrafana环境搭建方法及流程两种方式(docker和源码包)(一)-CSDN博客 Prometheusgrafana环境搭建rabbitmq(docker二进制两种方式安装)(二)-CSDN博客 Prometheusgrafana环境搭建m…

Java基础知识总结(第八篇):集合:Collection(List、Set)、Map、Collections 工具类

声明: 1. 本文根据韩顺平老师教学视频自行整理&#xff0c;以便记忆 2. 若有错误不当之处, 请指出 系列文章目录 Java基础知识总结&#xff08;第一篇&#xff09;&#xff1a;基础语法 Java基础知识总结&#xff08;第二篇&#xff09;&#x…

Matlab|计及需求侧响应日前—日内两阶段鲁棒备用优化

目录 1 主要内容 日前计划模型 日内调整模型 不确定集建模 2 部分代码 3 程序结果 4 下载链接 1 主要内容 该程序复现文章《计及需求侧响应日前—日内两阶段鲁棒备用优化》&#xff0c;以6节点系统为例&#xff0c;综合考虑风电出力不确定性与电力设备 N-k强迫停运&…

在linux下conda 创建虚拟环境失败的解决方法

报错内容一&#xff1a; Solving environment: failed CondaHTTPError: HTTP 000 CONNECTION FAILED for url <https://conda.anaconda.org/conda-forge/noarch/repodata.json> Elapsed: - An HTTP error occurred when trying to retrieve this URL. HTTP errors are…

【c++】类和对象(七)

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;c笔记仓 朋友们大家好&#xff0c;本篇文章来到类和对象的最后一部分 目录 1.static成员1.1特性 2.友元2.1引入&#xff1a;<<和>>的重载2.2友元函数2.3友元类 3.内部类4.匿名对象5.拷…

servlet个人博客系统的web自动化测试(六)

1. 测试前的准备工作 获取博客系统Chrome&webDriver测试环境搭建流程 : 熟悉项目针对核心流程设计测试用例&#xff08;手工测试用例&#xff09;将手工测试用例转换成自动化测试用例部署 手工测试用例 : 自动化测试用例 代码结构设计 &#xff1a; 初始化动作&#xff1a…

最新AI工具系统ChatGPT网站运营源码SparkAi系统V6.0版本,GPTs应用、AI绘画、AI换脸、垫图混图、Suno-v3-AI音乐生成大模型全支持

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支持GPT…

银行监管报送系统介绍(十五):金融审计平台

《“十四五”国家审计工作发展规划》中重点强调&#xff0c;金融审计&#xff1a;以防范化解重大风险、促进金融服务实体经济&#xff0c;推动深化金融供给侧结构性改革、建立安全高效的现代金融体系为目标&#xff0c;加强对金融监管部门、金融机构和金融市场运行的审计。 —…

Spring API 接口和自定义类来实现AOP(Spring学习笔记十)

1、什么是AOP 全称是 Aspect Oriented Programming 即&#xff1a;面向切面编程。是OOP&#xff08;面向对象编程&#xff09;的延续&#xff0c;也是Spring框架中的一个重要内容&#xff0c;是函数式编程的一种衍生泛型。简单的说他就是把我们程序重复的代码抽取出来&#xf…