之前一直没有自己实现过阴影,只是概念上有所了解,这次通过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. Resin开放远程调试端口

    Resin开放远程调试端口在启动加载的resin.xml中,找到  <server-default>, 在其下加入 <jvm-arg>-Xdebug</jvm-arg&g ...

  2. 【若泽大数据】玩转大数据之Spark零基础到实战

    https://www.bilibili.com/video/av29407581?p=1 若泽大数据官网 http://www.ruozedata.com/ tidb 系列三:有了sparkjdbc ...

  3. django的form 登录组件

    1. 了解form 组件的原理 1.建立好form组件 class>>> 2.了解需要先is_valid() 判断是否可以取值 成功 form.cleaned_data 查看成功认证 ...

  4. react小项目

    本章要讲述一个评价栏的制作. 首先先简单写一个ract组件来试试. index.html <!DOCTYPE html> <html> <head> <tit ...

  5. 第11节-BLE协议HCI层的硬件接口

    本篇博客由韦东山视频整理所得 如何控制链路层让其发出广播包.数据包?通过HCI层向它发出命令,也可以通过ATT层.L2CAP层向LL层发出数据. 学习资料: 蓝牙协议core_v5.0.pdf < ...

  6. LCD编程_显示文字

    在上篇博客中,实现了画点操作,然后在画点的基础上实现了画线.画圆的操作.实际上显示文字也是在画点的基础上实现的. 文字是由点组成的,那么这些点阵是在哪里获得的呢? 随便打开一个内核文件,搜索font, ...

  7. 201871010128-杨丽霞《面向对象程序设计(java)》第二周学习总结

    201871010128-杨丽霞<面向对象程序设计(java)>第二周学习总结 项目 内容 这个作业属于哪个课程 <https://www.cnblogs.com/nwnu-daiz ...

  8. DFS_BFS(深度优先搜索 和 广度优先搜索)

    package com.rao.graph; import java.util.LinkedList; /** * @author Srao * @className BFS_DFS * @date ...

  9. (HK1-2)海康相机直接连接电脑不经过路由器设置

    解决电脑无法通过网线直连海康摄像机的问题 https://blog.csdn.net/u014552102/article/details/86708371 一.现象:    通过博主的另外一篇博客h ...

  10. gnome 3 插件设置

    插件安装及管理方法 应该需提前在gnome-tweaks中打开user-theme,重启电脑后才可找到Add-ons Debian9 下在应用商店插件add-ons里进行选择安装,在应用商店已安装应用 ...