之前一直没有自己实现过阴影,只是概念上有所了解,这次通过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. antd快速开发(Form篇)

    antd快速开发(Form篇) 前言 由于一直在做中台业务,后台项目特别多,但是后台项目的特点是:大量的列表和大量表单,重复开发会降低效率,所以我这边总结了一下使用antd组件搭建form的快捷方法. ...

  2. __attribute__((weak))

    情况是这样的,碰到一个棘手的问题:我们不确定外部模块是否提供一个函数func,但是我们不得不用这个函数,即自己模块的代码必须用到func函数: extern int func(void); ..... ...

  3. 第二阶段冲刺(个人)——three

    今天的个人计划:选择功能界面的选择框排版设计.使得一些选择功能当点击鼠标事件后才会出现. 昨天做了什么?测试登录功能并优化. 遇到了什么困难?一些js函数的运用不熟悉,好多借助了百度.

  4. Anaconda3(4)安装pytorch

    安装链接 https://pytorch.org/ 0在anaconda3安装python3.6环境  https://blog.csdn.net/u012005313/article/details ...

  5. 为什么要使用 Go 语言?Go 语言的优势在哪里?

    golang主要特性 1.语法简单 舍弃语法糖,严格控制关键字 C++语法糖之多,令人发指,而C又太过于底层,容易出现自己造轮子的情况,如何在两者之间取舍,是每一个转向golang的工程师曾经思考过的 ...

  6. 以V8中js源码为例了解GitHub查看代码功能

    GitHub作为开源仓库,许多开源项目仓库这里,当然不乏十分优秀的,比如Node.V8,我一直比较好奇js源码,像java的话,因为环境是JDK,我们结合IDE很容易就能跳转到其源码内部去查看实现,但 ...

  7. centos7 中没有service iptables save指令来保存防火墙规则

    解决方法: systemctl stop firewalld  关闭防火墙yum install iptables-services 安装 iptables 服务systemctl enable ip ...

  8. 【技术博客】 Laravel 5.1单元测试(PHPUnit)入门

    目录 Laravel 5.1单元测试(PHPUnit)入门 简介 安装与配置 1. 安装 2. 配置 编写测试样例 1. 新建测试样例 2. 编写函数的测试 3. 编写Web功能测试 运行测试与查看结 ...

  9. 免费https证书

    https://certbot.eff.org/lets-encrypt/ubuntuother-nginx https://ruby-china.org/topics/31942 https://l ...

  10. pom.xml文件引入tools.jar

    最近做hbase开发时,引入相关jar包后,出现了以下错误 Missing artifact jdk.tools:jdk.tools:jar:1.8 绝对地址引用 <dependency> ...