之前一直没有自己实现过阴影,只是概念上有所了解,这次通过Demo进行实际编写操作。

总的来说没有什么可以优化的,倒是对于窗户这种可用面片代替的物体似乎能优化到贴图上,之前arm有个象棋屋的demo做过这个

来说回Shadowmap,主要思想是通过深度图可得到世界坐标位置,所以光源位置渲染一张场景深度图以得到光源位置像素点的世界坐标,

再对比主相机的像素点世界坐标,如果两个世界坐标距离小于误差则说明两者都能看见这个点,则这个点不在阴影内,否则在阴影区域内

当然实际做起来有许多更高效的做法。而A相机内的像素点如何切换到B相机这样的问题,可以通过投影变换来实现,也就是

Camera.main.WorldToViewportPoint。将世界坐标位置转换为视口坐标,0-1的范围,直接适用于贴图采样。

算是讲的比较简单直白,网上一些操作我都省了,那么来看看具体的操作步骤。

1.在光源子节点上挂载一个相机,用正交显示即可,但光源相机要可看见待投射阴影对象的模型。然后给这个光源相机挂载相机渲染深度的脚本,这里偷懒直接用OnRenderImage。

public class LightShadowMapFilter : MonoBehaviour
{
public Material mat; void Awake()
{
GetComponent<Camera>().depthTextureMode |= DepthTextureMode.Depth;
} void OnRenderImage(RenderTexture source, RenderTexture destination)
{
Graphics.Blit(source, destination, mat);
}
}

LightShadowMapFilter

2.这个脚本需要一个材质球参数,这个材质球的shader即为返回深度的Shader,但为了方便这里直接返回世界坐标信息,如下:

Shader "ShadowMap/DepthRender"//渲染深度
{
Properties
{
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD
ZTest Always
Cull Off
ZWrite Off Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag #include "UnityCG.cginc" struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
}; sampler2D _CameraDepthTexture;//光源相机传入的深度图 #define NONE_ITEM_EPS 0.99//如果没有渲染到物体就比较麻烦,所以设置一个EPS阈值 v2f vert(appdata_img v)
{
v2f o = (v2f);
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord.xy; return o;
} float4 GetWorldPositionFromDepthValue(float2 uv, float linearDepth)//通过深度得到世界坐标位置
{
float camPosZ = _ProjectionParams.y + (_ProjectionParams.z - _ProjectionParams.y) * linearDepth; float height = * camPosZ / unity_CameraProjection._m11;
float width = _ScreenParams.x / _ScreenParams.y * height; float camPosX = width * uv.x - width / ;
float camPosY = height * uv.y - height / ;
float4 camPos = float4(camPosX, camPosY, camPosZ, 1.0);
return mul(unity_CameraToWorld, camPos);
} fixed4 frag (v2f i) : SV_Target
{
float rawDepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
float linearDepth = Linear01Depth(rawDepth);
if (linearDepth > NONE_ITEM_EPS) return ;//如果没有物体则返回0
return fixed4(GetWorldPositionFromDepthValue(i.uv, linearDepth).xyz, );//如果有物体则返回世界坐标信息
}
ENDCG
}
}
}

ShadowMap/DepthRender

3.编辑器内给光源相机绑定RenderTexture到RenderTarget,直接在Project面板里创建,分辨率设置为1024即可,光源这部分就结束了。

4.接下来开始处理主相机的逻辑。根据官方论坛的信息camera WorldToViewportPoint的等价实现在这儿:

https://forum.unity.com/threads/camera-worldtoviewportpoint-math.644383/

由于合并到一个矩阵内不直观,最后一步投影坐标到NDC再到视口的操作放到shader中去处理。

那么先处理光源深度图和光源相机VP矩阵传入的脚本,也就是论坛帖子里前两步操作,如下:

using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine; public class ShadowMapArgumentUpdate : MonoBehaviour
{
public RenderTexture lightWorldPosTexture;
public Camera depthCamera; void Update()
{
var viewMatrix = depthCamera.worldToCameraMatrix;
var projMatrix = GL.GetGPUProjectionMatrix(depthCamera.projectionMatrix, false) * viewMatrix; Shader.SetGlobalTexture("_LightWorldPosTex", lightWorldPosTexture);
Shader.SetGlobalMatrix("_LightProjMatrix", projMatrix);
}
}

ShadowMapArgumentUpdate

注意投影矩阵要经过GL类的转换函数处理,防止OpenGL和DX不一致。随后这个脚本挂载至主相机或某个GameObject上都可

需要挂载的RenderTexture和深度相机就是之前创建的。

5.最后是接收Shadowmap的shader。并对两个世界坐标的像素位置进行距离上的比较,当然比较深度大小更常规一些。

Shader "ShadowMap/ShadowMapProcess"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog #include "UnityCG.cginc" struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
}; struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float3 worldPos : TEXCOORD1;
}; sampler2D _LightWorldPosTex;
sampler2D _MainTex;
matrix _LightProjMatrix;
float4 _MainTex_ST; //https://forum.unity.com/threads/camera-worldtoviewportpoint-math.644383/
float3 Proj2ViewportPosition(float4 pos)
{
float3 ndcPosition = float3(pos.x / pos.w, pos.y / pos.w, pos.z / pos.w);
float3 viewportPosition = float3(ndcPosition.x*0.5 + 0.5, ndcPosition.y*0.5 + 0.5, -ndcPosition.z); return viewportPosition;
} v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;//世界坐标 return o;
} #define EPS 0.01//两个像素点最小距离差 fixed4 frag (v2f i) : SV_Target
{
float4 col = tex2D(_MainTex, i.uv);
float3 lightViewportPosition = Proj2ViewportPosition(mul(_LightProjMatrix, float4(i.worldPos,)));
//这个就是Camera.main.WorldToViewport的后半部分处理 float4 lightWorldPos = tex2D(_LightWorldPosTex, lightViewportPosition.xy);
//既然是视口坐标了直接采样贴图即可 if (lightWorldPos.a > && distance(i.worldPos, lightWorldPos) > EPS) return col * 0.2;
//两个像素点距离大于误差则为阴影,当然小问题是免不了的,这个只出于学习目的。
return col;//不在阴影内则返回原始像素
}
ENDCG
}
}
}

ShadowMap/ShadowMapProcess

6.完成效果如下。

Shadowmap简易实现的更多相关文章

  1. WebGL简易教程(十二):包围球与投影

    目录 1. 概述 2. 实现详解 3. 具体代码 4. 参考 1. 概述 在之前的教程中,都是通过物体的包围盒来设置模型视图投影矩阵(MVP矩阵),来确定物体合适的位置的.但是在很多情况下,使用包围盒 ...

  2. WebGL简易教程(十四):阴影

    目录 1. 概述 2. 示例 2.1. 着色器部分 2.1.1. 帧缓存着色器 2.1.2. 颜色缓存着色器 2.2. 绘制部分 2.2.1. 整体结构 2.2.2. 具体改动 3. 结果 4. 参考 ...

  3. WebGL简易教程——目录

    目录 1. 绪论 2. 目录 3. 资源 1. 绪论 最近研究WebGL,看了<WebGL编程指南>这本书,结合自己的专业知识写的一系列教程.之前在看OpenGL/WebGL的时候总是感觉 ...

  4. .NET里简易实现AOP

    .NET里简易实现AOP 前言 在MVC的过滤器章节中对于过滤器的使用就是AOP的一个实现了吧,时常在工作学习中遇到AOP对于它的运用可以说是很熟练了,就是没想过如果自己来实现的话是怎么实现的,性子比 ...

  5. 在.Net中实现自己的简易AOP

    RealProxy基本代理类 RealProxy类提供代理的基本功能.这个类中有一个GetTransparentProxy方法,此方法返回当前代理实例的透明代理.这是我们AOP实现的主要依赖. 新建一 ...

  6. .NET Core的文件系统[5]:扩展文件系统构建一个简易版“云盘”

    FileProvider构建了一个抽象文件系统,作为它的两个具体实现,PhysicalFileProvider和EmbeddedFileProvider则分别为我们构建了一个物理文件系统和程序集内嵌文 ...

  7. 自己来实现一个简易的OCR

    来做个简易的字符识别 ,既然是简易的 那么我们就不能用任何的第三方库 .啥谷歌的 tesseract-ocr, opencv 之类的 那些玩意是叼 至少图像处理 机器视觉这类课题对我这种高中没毕业的人 ...

  8. php+websocket搭建简易聊天室实践

    1.前言 公司游戏里面有个简单的聊天室,了解了之后才知道是node+websocket做的,想想php也来做个简单的聊天室.于是搜集各种资料看文档.找实例自己也写了个简单的聊天室. http连接分为短 ...

  9. 用Go实现的简易TCP通信框架

    接触到GO之后,GO的网络支持非常令人喜欢.GO实现了在语法层面上可以保持同步语义,但是却又没有牺牲太多性能,底层一样使用了IO路径复用,比如在LINUX下用了EPOLL,在WINDOWS下用了IOC ...

随机推荐

  1. MySql || 快速创建100w条记录

    平时每个开发者都会讨论数据量大时,sql的优化问题.但是并不是每个人都会有100w的数据量可以用来实战,那么今天我们就自己动手,模拟一个100w数据量的表. 创建原理 其实创建的方法有很多,有快的也有 ...

  2. nfs—文件转换器

    前端时间,在做一次设备升级时对nfs有了新的认识. nfs一般认为是文件共享服务器,但在实际的工作环境中,业务服务器有诸多限制,需要有加密隔离措施等等,版本升级和功能调试不同于平时的实验环境. 特别好 ...

  3. Natas25-writeup

    前言 题目链接: http://natas25.natas.labs.overthewire.org 做这一题花了一些时间,也是由于自己知识点掌握不足,所以分享下解题过程. 题目分析 首先,登录后看到 ...

  4. [codevs3044]矩形面积求并

    题目描述 Description 输入n个矩形,求他们总共占地面积(也就是求一下面积的并) 输入描述 Input Description 可能有多组数据,读到n=0为止(不超过15组) 每组数据第一行 ...

  5. 运算符-day04

    算数运算符 +.-.*././/(地板除.取商).%.** 依次为:加.减.乘.除.取整.取余.幂 比较运算符 >.<.==.>=.<=.!= 运算符优先级跟数学一样,可以不用 ...

  6. 元素的alt和title有什么异同?

    ①alt作为图片的替代文字出现,title作为图片的解释文字出现. ②alt属性应用较少,如img.area.input中,title应用较多,如a.form.input.还有div.p这些块级元素都 ...

  7. md5-js加密

    JS-MD5加密/html页面使用 大家都知道,传输明文信息很不安全,尤其像密码.卡号等这些敏感私密的信息,更不能暴露出去.在这里给大家介绍一种在前端JS中的MD5加密算法(因为要匹配的后台数据是MD ...

  8. c# 文件夹权限

    /// <summary>         /// 创建文件路径         /// </summary>         /// <param name=" ...

  9. 第03组 Beta冲刺(4/4)

    队名:不等式方程组 组长博客 作业博客 团队项目进度 组员一:张逸杰(组长) 过去两天完成的任务: 文字/口头描述: 制定了初步的项目计划,并开始学习一些推荐.搜索类算法 GitHub签入纪录: 暂无 ...

  10. React 获取真实Dom v8.6.2版本

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...