IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 技术美术知识学习4300:实时阴影 -> 正文阅读

[游戏开发]技术美术知识学习4300:实时阴影

代码部分为Unity实现屏幕空间的实时阴影
学习教程来自:【技术美术百人计划】图形 4.3 实时阴影介绍

笔记

1. 基于图片的实时阴影技术

主流方法之一,把阴影生成为一张图片

1.1 平面投影阴影


将阴影投影在一个平面上。
缺点:必须是平面,产生阴影的物体必须介于光和平面之间
为了解决上一个方法的缺点之一(只能在平面上产生阴影)。
步骤简述:为物体多设置一个相机产生阴影纹理,与被阴影覆盖的表面的纹理进行混合得到阴影效果,在Unity中使用Projector组件实现

1.2 阴影映射(Shadow Map)

概念:从光源的位置和角度获取的深度图
核心思想:对比Shadow Map和摄像机视角的深度图,片元在Shadow Map中的值小于后者时,产生阴影


1.3 屏幕空间阴影映射

Unity中的阴影映射实现(即屏幕空间的阴影映射)


步骤:

  1. 屏幕空间的深度贴图
  2. 光源方向的Shadow Map

  1. 屏幕空间下进行对1、2的结果进行计算得到屏幕空间的阴影纹理
  2. 绘制3中的结果

2. 阴影映射的优化

2.1 自阴影问题


也叫Z-Fighting、阴影瑕疵、阴影粉刺(Surface Acne),由于阴影贴图分辨率(其分辨率低,但是相机得到的深度图分辨率高)、离散采样、数值精度等问题产生的错误的自阴影
解决办法:

  1. 深度偏移(Depth Bias):设置一个差值的阈值,减少阴影的产生。太大会导致Peter Panning(阴影与投影者脱节)
  2. 法线偏移(Normal Bias):上一条中的方向为视角方向,本方法在法线方向上偏移
    补充:偏移单位为纹素(1/分辨率),只在阴影深度测试时使用,不影响其他效果

2.2 走样问题

由于采样产生

2.2.1 透视走样


Shadow Map本身大小均匀,但透视投影完成后采样变得不均匀,由此产生了走样(距离观察者近的元素产生走样)
解决:

  1. 在Shadow Map生成时进行透视投影,以保持均匀性的一致
  2. 级联阴影映射(Unity的解决办法):划分视锥体,得到相同大小的Shadow Map(近处的质量更高)


我猜这也是为什么上边1.3中的Shadow Map有4个

2.2.2 重采样

采样贴图时产生的误差
解决:滤波(PCF滤波),对滤波核的每一个采样点,对比中间的值后划分为blocked和visible这2种状态,输出shadow=visible/(visible+blocked)。实现方式有很多种(不同的采样个数、不同的滤波函数)


作业

1. 总结实时阴影系统的优化方案

内容来自以上笔记:

方法名称方法解决的问题
深度偏移使用一个偏移值来避免深度比较时产生的误差自阴影
法线偏移沿法线方向进行偏移自阴影
透视投影在Shadow Map生成时进行透视投影,以保持均匀性的一致透视走样
级联阴影映射从近到远划分视锥体,得到相同大小的Shadow Map透视走样
PCF滤波对阴影贴图滤波,得到shadow值重采样

2. 自己实现阴影系统

Tips:可以把模型的背面渲染出来作为阴影的一个Pass,来优化和避免一些问题 //TODO:
做这个是四处看代码,左抄点,又抄点,混一起成了。
参考来源:
Unity的实时阴影-ShadowMap实现原理:粘了不好使(盲猜shadowmap的矩阵转换不对),但学到大概知道要这么2个shader,一个脚本
Unity基础6 Shadow Map 阴影实现:对着上边的代码看了一下每一步大概的实现
Unity实时阴影实现——Shadow Mapping:然后看到这篇,比较接近能直接来拿粘贴的程度了,但还差点,大概是在update里调用一下函数,先调哪个后调那个不太确定。
上一条作者的github:完结,里边有一个老一点版本的shadowmap实现(对应PPT,1个shadowMap的Shader,1个屏幕空间深度的Shader,1个比较这2个深度图并计算阴影的Shader),现在对着上边的文章改成在接收阴影的材质shader中比较深度计算阴影,完成实现(2个shader,1个计算shadowMap,1个计算阴影)
总结一下实现过程,好像和上边PPT讲的不太一样:

  1. 从光源方向创建相机,渲染深度图的得到shadowMap
  2. 保存一个变换矩阵_gWorldToShadow,将阴影接收者的世界空间坐标转换到shadowMap对应的空间中(和PPT中不同,这里在shadowMap对应的空间下做collector计算,而不是将shadowMap转换到屏幕空间)
  3. 转换后的坐标,xy值作为UV采样shadowmap得到sampleDepth,z作为深度depth(这些值都经过了一些处理达到0-1),这里的depth并不是渲染深度得到的,而是一个位置信息,如果大于shadowMap采样的值,证明被挡住了,应该有阴影产生
  4. 用sampleDepth和depth计算得到阴影(sampleDepth可以for循环多采样几次计算模糊)
    比较关键的地方就是把接收者的顶点位置转换到shadowMap对应的空间下(xy值作为UV采样shadowmap得到sampleDepth,z作为深度depth)
v2f vert (appdata_full v) 
{
    v2f o;
    o.pos = UnityObjectToClipPos (v.vertex);
    float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
    o.shadowCoord = mul(_gWorldToShadow, worldPos);//转换到shadowMap对应的空间

    return o; 
}

fixed4 frag (v2f i) : COLOR0 
{            
    // shadow
    i.shadowCoord.xy = i.shadowCoord.xy/i.shadowCoord.w;
    float2 uv = i.shadowCoord.xy;
    uv = uv*0.5 + 0.5; //(-1, 1)-->(0, 1)

    float depth = i.shadowCoord.z / i.shadowCoord.w;
    #if defined (SHADER_TARGET_GLSL)
    depth = depth*0.5 + 0.5; //(-1, 1)-->(0, 1)
    #elif defined (UNITY_REVERSED_Z)
    depth = 1 - depth;       //(1, 0)-->(0, 1)
    #endif

    // sample depth texture
    // 模糊前
    //float4 col = tex2D(_gShadowMapTexture, uv);
    //float sampleDepth = DecodeFloatRGBA(col);
    //float shadow = sampleDepth < depth ? _gShadowStrength : 1;
    // 模糊后
    float shadow = PCFSample(depth, uv);

    return shadow;
}
这个是shadowMap,不过是EncodeFloatRGBA之后的

上图:


刚开始有个小问题:之前建模的钢铁侠面罩模型只有一个面,没有封口,就是背面是透的,结果来到这里正面面对阳光的时候,竟然没有阴影,开了framedebug,原来是cull front去掉了正面。如图



注释掉好了,看来这个就是开头说的直接用背面作为渲染阴影的Pass了,这样做的好处猜测应该是,反正shadowMap空间下正面和背面一定会相互遮挡,不如只考虑一个面,而背面的复杂度应该又比正面低一点,所以这样是效率比较高的


最后是边缘模糊PCF Soft Shadow(Percentage Closer Filtering),代码也来自上边知乎大佬的帖子,原理就是对ShadowMap围绕着中心点采样了9次,每次都和中心点的depth比较后累加,再除9

float PCFSample(float depth, float2 uv)
{
    float shadow = 0.0;
    for (int x = -1; x <= 1; ++x)
    {
        for (int y = -1; y <= 1; ++y)
        {
            float4 col = tex2D(_gShadowMapTexture, uv + float2(x, y) * _gShadowMapTexture_TexelSize.xy);
            float sampleDepth = DecodeFloatRGBA(col);
            shadow += sampleDepth < depth ? _gShadowStrength : 1;//每一个采样点都与深度值相比较,累加
        }
    }
    return shadow /= 9;
}
模糊前

模糊后

远点效果看着还行

最后,这些代码也上传git了,感兴趣的可以拉下来看看(本篇内容在/Scene/4300)
git地址

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-10-04 13:08:06  更:2021-10-04 13:08:16 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/28 2:34:20-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码