当前位置: 首页 > 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);
}

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

相关文章:

Threejs中的Shadow Mapping(阴影贴图)

简而言之&#xff0c;步骤如下&#xff1a; 1.从灯光位置视点&#xff08;阴影相机&#xff09;创建深度图。 2.从相机的位置角度进行屏幕渲染&#xff0c;在每个像素点&#xff0c;比较由阴影相机的MVP矩阵计算的深度值和深度图的值的大小&#xff0c;如果深度图值小的话&…...

本质安全设备标准(IEC60079-11)的理解(四)

本质安全设备标准&#xff08;IEC60079-11&#xff09;的理解&#xff08;四&#xff09; 对于标准中“Separation”的理解 IEC60079-11使用了较长的篇幅来说明设计中需要考虑到的各种间距&#xff0c; 这也从一定程度上说明了间距比较重要&#xff0c;在设计中是需要认真考虑…...

(record)QEMU安装最小linux系统——TinyCore(命令行版)

文章目录QEMU安装最小linux系统——TinyCore参考QEMU使用qemu创建tinycore虚拟机再次启动文件保存QEMU安装最小linux系统——TinyCore 简单记录安装过程和记录点 参考 [原创] qemu 与 Tiny Core tinycore的探索 QEMU qemu不多介绍&#xff0c;这里是在WSL2上安装的linux版…...

C++中的cast类型转换

reinterpret_cast用法&#xff1a;reinpreter_cast<type-id> (expression)type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数&#xff0c;也可以把一个整数转换成一个指针。这个操作符能够在非相关的类型之间转换。操作结果…...

西瓜数据集读取的详细解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。喜欢通过博客创作的方式对所学的知识进行总结与归纳,不仅形成深入且独到的理…...

Mac开发环境配置

一、mac 安装homebrew 1. 必要性 ​ homebrew可以通过bash命令快速安装配置开发环境&#xff0c;并且在大多数情况下可以实现环境的自动配置。&#xff08;一键安装配置&#xff09; 2. 收益 ​ 节省开发环境工具配置时间&#xff0c;提高人效。 3. 安装步骤 打开mac终端…...

概率论面试题1:玫瑰花

概率论面试题 1. 一个活动&#xff0c;n个女生手里拿着长短不一的玫瑰花&#xff0c;无序的排成一排&#xff0c;一个男生从头走到尾&#xff0c;试图拿更长的玫瑰花&#xff0c;一旦拿了一朵就不能再拿其他的&#xff0c;错过了就不能回头&#xff0c;问最好的策略&#xff1…...

【DGL】图分类

目录概述数据集定义Data LoaderDGL中的batched graph定义模型训练参考概述 除了节点级别的问题——节点分类、边级别的问题——链接预测之外&#xff0c;还有整个图级别的问题——图分类。经过聚合、传递消息得到节点和边的新的表征后&#xff0c;映射得到整个图的表征。 数据…...

时间复杂度的计算(2023-02-10)

时间复杂度的计算 时间复杂度的计算分为三大类&#xff1a;一层循环、二层循环和多层循环。 一层循环 1.找出循环趟数t及每轮循环i的变化值 2.确立循环停止的条件 3.得出t与i之间的关系 4.联立两式&#xff0c;得出结果 eg: void fun(int n) {int i0;while (i*i*i<n)i;…...

测试开发之Django实战示例 第六章 追踪用户行为

第六章 追踪用户行为在之前的章节里完成了小书签将外站图片保存至本站的功能&#xff0c;并且实现了通过jQuery发送AJAX请求&#xff0c;让用户可以对图片进行喜欢/不喜欢操作。这一章将学习如何创建一个用户关注系统和创建用户行为流数据&#xff0c;还将学习Django的信号框架…...

红米9a手动root方法

简介 已知红米6A/6/9/9A/9C/10A机器都可以快速解锁BL&#xff0c;无任何变砖风险 并且秒解锁BL后和官方解锁一样&#xff0c;无任何其他不良影响。推荐大家使用官网解锁&#xff0c;需要等待7天。 ​ BootLoader BootLoader是在操作系统内核运行之前运行的一段小程序。其实…...

Open3D 点云最小二乘法拟合平面(剔除噪声,Python版本)

除了诱惑之外,我可以抵抗任何事物。 ----王尔德 文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 这个算法的思路很简单,就是通过剔除一些异常点来拟合更为合适的平面,具体过程如下所示: 1、首先使用最小二乘法拟合一个平面系数的初值。 2、计算所有有效点到拟合…...

【SpringBoot】简述springboot项目启动数据加载内存中的三种方法

一、前言一般来说&#xff0c;SpringBoot工程环境配置放在properties文件中&#xff0c;启动的时候将工程中的properties/yaml文件的配置项加载到内存中。但这种方式改配置项的时候&#xff0c;需要重新编译部署&#xff0c;考虑到这种因素&#xff0c;今天介绍将配置项存到数据…...

【一文速通】各种机器学习算法的特点及应用场景

近邻 (Nearest Neighbor)KNN算法的核心思想是&#xff0c;如果一个样本在特征空间中的K个最相邻的样本中的大多数属于某一个类别&#xff0c;则该样本也属于这个类别&#xff0c;并具有这个类别上样本的特性。该方法在确定分类决策上只依据最邻近的一个或者几个样本的类别来决定…...

多传感器融合定位十四-基于图优化的定位方法

多传感器融合定位十四-基于图优化的定位方法1. 基于图优化的定位简介1.1 核心思路1.2 定位流程2. 边缘化原理及应用2.1 边缘化原理2.2 从滤波角度理解边缘化3. 基于kitti的实现原理3.1 基于地图定位的滑动窗口模型3.2 边缘化过程4. lio-mapping 介绍4.1 核心思想4.2 具体流程4.…...

PHP基于TCPDF第三方类生成PDF文件

最近在研发招聘的系统 遇到了这个问题 转换pdf 折腾了很久 分享一下PHP基于TCPDF第三方类生成PDF文件最近遇到一个需求&#xff0c;需要根据数据库的字段生成表格式的PDF文件并发送邮箱第一步、我们先去官网上面去下载tcpdf的类&#xff1a;http://www.tcpdf.org/或者是从githu…...

SpringCloud(19):Sentinel定义资源的方式

Sentinel除了基本的定义资源的方式之外,还有其他的定义资源的方式,具体如下: 抛出异常的方式定义资源返回布尔值方式定义资源异步调用支持注解方式定义资源主流框架的默认适配1 抛出异常的方式定义资源 Sentinel中的SphU包含了try-catch风格的API。用这种方式,当资源发生了…...

前端 ES6 之 Promise 实践应用与控制反转

Promise 主要是为解决程序异步处理而生的&#xff0c;在现在的前端应用中无处不在&#xff0c;已然成为前端开发中最重要的技能点之一。它不仅解决了以前回调函数地狱嵌套的痛点&#xff0c;更重要的是它提供了更完整、更强大的异步解决方案。 同时 Promise 也是前端面试中必不…...

LightGBM

目录 1.LightGBM的直方图算法(Histogram) 直方图做差加速 2.LightGBM得两大先进技术(GOSS&EFB) 2.1 单边梯度抽样算法(GOSS) 2.2 互斥特征捆绑算法(EFB) 3.LightGBM得生长策略(leaf-wise) 通过与xgboost对比&#xff0c;在这里列出lgb新提出的几个方面的技术 1.Ligh…...

Science:北京脑研究中心李莹实验室揭示性满足感的分子机制

短暂的社交经历&#xff08;例如&#xff0c;性经历&#xff09;可导致内部状态的长期变化并影响社会行为&#xff0c;如交配、攻击。例如&#xff0c;在成功交配射精后&#xff0c;许多物种迅速表现出对交配倾向的抑制有数小时、数天或更长时间&#xff0c;这种效应称为性满足…...

Element UI框架学习篇(三)

Element UI框架学习篇(三) 实现简单登录功能(不含记住密码) 1 准备工作 1.1 在zlz包下创建dto包,并创建userDTO类(传输对象) package com.zlz.dto;import lombok.Data;/* DTO 数据传输对象 用户表的传输对象 调用控制器传参使用 VO 控制器返回的视图对象 与页面对应 PO 数据…...

尚硅谷的尚融宝项目

先建立一个Maven springboot项目 进来先把src删掉&#xff0c;因为是一个父项目&#xff0c;我们删掉src之后&#xff0c;pom里配置的东西&#xff0c;也能给别的模块使用。 改一下springboot的版本号码 加入依赖和依赖管理&#xff1a; <properties><java.versi…...

12 Day:内存管理

前言&#xff1a;今天我们要完成我们操作系统的内存管理&#xff0c;以及一些数据结构和小组件的实现&#xff0c;在此之前大家需要了解我们前几天一些重要文件的内存地址存放在哪&#xff0c;以便我们更好的去编写内存管理模块 一&#xff0c;实现ASSERT断言 不知道大家有没有…...

linux基本功系列之lsof命令实战

文章目录前言一. lsof命令介绍二. 语法格式及常用选项三. 参考案例3.1 显示系统打开的文件3.2 查找某个文件相关的进程3.3 列出某个用户打开的文件信息3.4 列出某个程序进程所打开的文件信息3.5 查看某个进程号打开的文件3.6 列出所有的网络连接3.7 列出谁在使用某个端口3.8 恢…...

基础篇:02-SpringCloud概述

1.SpringCloud诞生 基于前面章节&#xff0c;我们深知微服务已成为当前开发的主流技术栈&#xff0c;但是如dubbo、zookeeper、nacos、rocketmq、rabbitmq、springboot、redis、es这般众多技术都只解决了一个或一类问题&#xff0c;微服务并没有一个统一的解决方案。开发人员或…...

【软件测试】软件测试工作上95%会遇到的问题,你遇到多少?

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 1、测试负责人要进行…...

4.5.4 LinkedList

文章目录1.特点2.常用方法3.练习:LinkedList测试1.特点 链表,两端效率高,底层就是链表实现的 List接口的实现类&#xff0c;底层的数据结构为链表&#xff0c;内存空间是不连续的 元素有下标&#xff0c;有序允许存放重复的元素在数据量较大的情况下&#xff0c;查询慢&am…...

Python之FileNotFoundError: [Errno 2] No such file or directory问题处理

错误信息&#xff1a;FileNotFoundError: [Errno 2] No such file or directory: ../AutoFrame/temp/report.xlsx相对于当前文件夹的路径&#xff0c;其实就是你写的py文件所在的文件夹路径&#xff01;python在对文件的操作时&#xff0c;需要特别注意文件地址的书写。文件的路…...

C语言中耳熟能详的printf与scanf

没有什么比时间更有说服力了&#xff0c;因为时间无需通知我们就可以改变一切了。---余华《活着》大家好&#xff0c;今天给大家分享的是C语言中的scanf与printf函数&#xff0c;一提起这两个函数&#xff0c;大家可能觉得这不就是打印和输入嘛&#xff1f;有什么可以说的&…...

【数据结构】复杂度讲解

目录 时间复杂度与空间复杂度&#xff1a;&#xff1a; 1.算法效率 2.时间复杂度 3.空间复杂度 4.常见时间复杂度以及复杂度OJ练习 时间复杂度与空间复杂度&#xff1a;&#xff1a; 什么是数据结构? 数据结构中是计算机存储,组织数据的方式,指相互之间存在一种或多种特定关…...

广州商城网站建设报价/百度app客服电话

一、Context 全局的环境对象,提供了很多方便的操作&#xff0c;帮助我们快速的获取数据,进行一些常规的操作。 1.1、获取路径 getFilesDir()等同于/data/data/包名/files/ File file new File(getFilesDir(),"info.txt"); 1.2、缓存文件路径 getCacheDir()等同于/da…...

wordpress 国内云/想做个网络推广

编者按Branch-and-Cut 是求解整数规划或混合整数规划问题最常用的算法之一。通常&#xff0c;把全部可行解空间反复地分割为越来越小的子集&#xff0c;称为分支&#xff1b;并且对每个子集内的解集计算一个目标下界&#xff08;对于最小值问题&#xff09;&#xff0c;称为定界…...

南通优普网站建设团队/百度图片识别搜索

相关操作学习记录备忘录 echo offrem 1、添加winrar压缩软件到系统环境变量&#xff0c;才可以压缩文件 rem 2、设置变量 不能有空格 "set a 123"(报错) "set a123"(正确) rem 3、强制删除文件夹 /s /q rem 4、重命名文件 第二个参数必须是文件…...

建设工程施工包括哪些工程/广州seo优化公司

在golang的开发过程中&#xff0c;当我们使用orm的时候&#xff0c;常常需要将数据库表对应到golang的一个struct&#xff0c;这些struct会携带orm对应的tag,就像下面的struct定义一样&#xff1a;gotype InsInfo struct {Connections string gorm:"column:connections&qu…...

苏州快速建设网站公司/免费好用的网站

建立Empty Application&#xff0c;新建Storyboard并添加View Controller 控件后运行为空白&#xff0c;并报错 控制台显示&#xff1a; 2012-07-18 15:21:57.338 apress-8[664:f803] Application windows are expected to have a root view controller at the end of applicat…...

镇江网站建设 的公司/腾讯企点下载

什么是gerber文件 Gerber文件是所有电路设计软件都可以产生的文件&#xff0c;在电子组装行业又称为模版文件&#xff08;stencil data&#xff09;,在PCB制造业又称为光绘文件。可以说Gerber文件是电子组装业中最通用最广泛的文件格式。因此对于一个电子生产企业&#xff0c;拥…...