Threejs中的Shadow Mapping(阴影贴图)

news2024/11/28 13:49:11

简而言之,步骤如下:

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 clip
    gl_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_viewer

const 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 1
    bias = clamp(bias, 0.0, 0.01);
    
    float shadowFactor = step(depth_shadowCoord - bias, depth_depthMap);

    gl_fragColor = vec4(vec3(shadowFactor), 1.0);
}

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

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

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

相关文章

Office Server Document Converter Lib SDK Crack

关于 Office Server 文档转换器 (OSDC) 无需 Microsoft Office 或 Adob​​e 软件即可快速准确地转换文档。antennahouse.com Office Server 文档转换器 (OSDC) 会将您在 Microsoft Office&#xff08;Word、Excel、PowerPoint&#xff09;中创建的重要文档转换为高质量的 PDF …

【编程基础之Python】2、安装Python环境

【编程基础之Python】2、安装Python环境安装Python环境在Windows上安装Python验证Python运行环境在Linux上安装Python验证Python运行环境总结安装Python环境 所谓“工欲善其事&#xff0c;必先利其器”。在学习Python之前需要先搭建Python的运行环境。由于Python是跨平台的&am…

机器学习之K-means原理详解、公式推导、简单实例(python实现,sklearn调包)

目录1. 聚类原理1.1. 无监督与聚类1.2. K均值算法2. 公式推导2.1. 距离2.2. 最小平方误差3. 实例3.1. python实现3.2. sklearn实现4. 运行&#xff08;可直接食用&#xff09;1. 聚类原理 1.1. 无监督与聚类 在这部分我今天主要介绍K均值聚类算法&#xff0c;在这之前我想提一…

01-幂等性解释,问题及常用解决方案

目录 1. 幂等性简介 2. 后端如何解决幂等性问题 2.1 数据库层面 -> 2.1.1 防重表 -> 2.1.2 数据库悲观锁(不建议,容易出现死锁情况) -> 2.1.3 数据库乐观锁 -> 2.1.4 乐观锁CAS算法原理 2.2 锁层面 2.3 幂等性token层面 -> 2.3.1 简介文字描述: …

Java开发 - 问君能有几多愁,Spring Boot瞅一瞅。

前言 首先在这里恭祝大家新年快乐&#xff0c;兔年大吉。本来是想在年前发布这篇博文的&#xff0c;奈何过年期间走街串巷&#xff0c;实在无心学术&#xff0c;所以不得不放在近日写下这篇Spring Boot的博文。在还没开始写之前&#xff0c;我已经预见到&#xff0c;这恐怕将是…

中国社科院与美国杜兰大学金融管理硕士,让我们相遇在春暖花开时

在芸芸众生中&#xff0c;能拥有志同道合的朋友是一件多么幸运的事。人们常说&#xff1a;你是谁&#xff0c;就会遇见谁。走过半生才知道&#xff0c;看似命中注定的遇见谁、发生的事&#xff0c;其实都取决于自己。只有自己足够优秀&#xff0c;才能遇到更优秀的别人。在这个…

IT人的晋升之路——关于人际交往能力的培养

对于咱们的程序员来说&#xff0c;工作往往不是最难的&#xff0c;更难的是人际交往和关系的维护处理。很多时候我们都宁愿加班&#xff0c;也不愿意是社交&#xff0c;认识新的朋友&#xff0c;拓展自己的圈子。对外的感觉就好像我们丧失了人际交往能力&#xff0c;是个呆子&a…

【chatGPT】持续火热一路狂飙,简单了解下TA的功能和示例代码吧

&#x1f389;&#x1f389; 最近chatGPT持续火爆&#xff0c;一路狂飙&#xff0c;对应如何注册和使用的优质文章非常多。 所以&#xff0c;此篇文章除了整理chatGPT文章外&#xff0c;主要是讲解如何获取API Key进行接口的调用&#x1f389;&#x1f389; 目录1、chatGPT解读…

蓝牙单点技术实现路径介绍

本文主要介绍蓝牙设备与手机一对一相连的 蓝牙单点 技术。 准备工作 系统要求&#xff1a;蓝牙使用需要安卓 4.3 以及以上版本&#xff0c;智能生活 App SDK 从安卓 4.4 开始支持。Manifest 权限&#xff1a; <uses-permission android:name"android.permission.ACCE…

Fluent Python 笔记 第 3 章 字典和集合

3.1 泛映射类型 只有可散列 的数据类型才能用作这些映射里的键 字典构造方法&#xff1a; >>> a dict(one1, two2, three3) >>> b {one: 1, two: 2, three: 3} >>> c dict(zip([one, two, three], [1, 2, 3])) >>> d dict([(two, 2…

5. Spring 事务

文章目录1. Spring 事务简介2. Spring 事务角色3. Spring 事务属性3.1 事务配置3.2 案例&#xff1a;转账业务追加日志3.3 事务传播行为1. Spring 事务简介 Spring 事务作用&#xff1a;在数据层或业务层保障一系列的数据库操作同成功、同失败。 数据层有事务我们可以理解&am…

多传感器融合定位十三-基于图优化的建图方法其二

多传感器融合定位十二-基于图优化的建图方法其二3.4 预积分方差计算3.4.1 核心思路3.4.2 连续时间下的微分方程3.4.3 离散时间下的传递方程3.5 预积分更新4. 典型方案介绍4.1 LIO-SAM介绍5. 融合编码器的优化方案5.1 整体思路介绍5.2 预积分模型设计Reference: 深蓝学院-多传感…

Vue3 - 自定义指令封装

Vue3 - 自定义指令封装一. 自定义指令封装1.1 全局/局部注册自定义聚焦指令1.2 自定义指令相关参数1.3 自定义指令参数传递二. 总结一. 自定义指令封装 vue中有很多内置的指令&#xff0c;我们一般在开发中也经常用到&#xff0c;比如v-if&#xff0c;v-for等等。那么本篇文章…

Vue极简使用

Vue安装Vue模板语法安装Vue 安装nodejs 这里我安装的是14.5.4版本 https://nodejs.org/download/release/v14.15.4/解压后配置一下环境变量就行 安装cnpm镜像 (这个安装的版本可能过高&#xff0c;后面安装Vue可能出问题) npm install -g cnpm --registryhttps://registry…

二十二、Gtk4-ListView

GTK 4添加了新的列表对象GtkListView、GtkGridView和GtkColumnView。这个新特性在Gtk API参考—列表小构件概述中有描述。 GTK 4还有其他实现列表的方法。它们是GtkListBox和GtkTreeView&#xff0c;它们是从GTK 3接管的。在Gtk开发博客中有一篇关于Matthias Clasen所写的列表…

vscode执行Python输出exited with code=9009 in 0.655 seconds

vscode执行Python输出exited with code9009 in 0.655 seconds 想用vscode写个脚本&#xff0c;用自己电脑配置了下vscode的python环境&#xff0c;结果点击右上角三角图标运行时却只会输出exited with code9009 in 0.655 seconds 这就不太理解了&#xff0c;我在公司时是能正…

linux性能分析 性能之巅学习笔记和内容摘录

本文只是在阅读《性能之巅》的过程中&#xff0c;对一些觉得有用的地方进行的总结和摘录&#xff0c;并附加一些方便理解的材料&#xff0c;完整内容还请阅读Gregg的大作 概念和方法 性能分析领域一词的全栈代表了整个操作系统的软硬件在内的所有事物 软件生命周期和性能规划…

LabWindows CVI 2017开发笔记--串口API

参考资料&#xff1a;https://download.csdn.net/download/Stark_/87424565?spm1001.2014.3001.5501 转载请注明出处&#xff1a;https://blog.csdn.net/Stark_/article/details/128966962?spm1001.2014.3001.5501 打开串口OpenComConfig OpenComConfig 打开一个串行并进行…

HTML-CSS-js教程

HTML 双标签<html> </html> 单标签<img> html5的DOCTYPE声明 <!DOCTYPE html>html的基本骨架 <!DOCTYPE html> <html> </html>head标签 用于定义文档的头部。文档的头部包含了各种属性和信息&#xff0c;包括文档的标题&#…

【成为架构师课程系列】架构设计中的核心思维方法

架构设计中的核心思维方法 目录 前言 #一、抽象思维 #二、分层思维 #三、分治思维 #四、演化思维 #五、如何培养架构设计思维