之前一直没有自己实现过阴影,只是概念上有所了解,这次通过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. Keras 训练一个单层全连接网络的线性回归模型

    1.准备环境,探索数据 import numpy as np from keras.models import Sequential from keras.layers import Dense im ...

  2. 201871010135 张玉晶 《面向对象程序设计(java)》第二周学习总结

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

  3. js语法中一些容易被忽略,但会造成严重后果的细节

    一.复杂数据类型-“对象”的地址引用方式,不理解清楚,会出大乱子 复习一下基础概念(老司机略过): JS的数据可以分为简单类型(数字.字符串.布尔值.null和undefined)和 复杂数据类型(对 ...

  4. keil中使用——变参数宏__VA_ARGS__

    本文说的__VA_ARGS__,就是一个可变参数宏,与printf中可变参数的宏定义一个道理,是新C99规范中增加的. __VA_ARGS__详情内容 1.关于__VA_ARGS__ __VA_ARG ...

  5. RHCE试题解析

    环境准备 yum-config-manager --add-repo=ADDREPO vim /etc/yum.conf gpgcheck=0(1=on,0=off)   增加指定repo源,关闭签名 ...

  6. 修改/etc/docker/daemon.json中的log-opts配置发现无效 docker 限制日志大小

    https://colobu.com/2018/10/22/no-space-left-on-device-for-docker/ 在/etc/docker/daemon.json中修改或添加log- ...

  7. postfix发邮件失败,日志和postqueue -p提示No route to host

    1.  防火墙未放行相关端口 2

  8. wireshark-wincap安装问题

    winpcap关键模块 32位系统: C:\Windows\system32\wpcap.dll C:\Windows\system32\Packet.dll C:\Windows\system32\ ...

  9. 回溯法 | 图的m着色问题

    学习链接:算法 图的M着色问题 虽然今早9点才醒来,10点才来教室,但是coding得很高效.吃个早餐,拉个粑粑的时间,就把算法书上的[图的m着色]问题看明白了,大脑里也形成了解决问题的框架. 其实这 ...

  10. bzoj3589 动态树 求链并 容斥

    bzoj3589 动态树 链接 bzoj 思路 求链并. 发现只有最多5条链子,可以容斥. 链交求法:链顶是两条链顶深度大的那个,链底是两个链底的\(lca\) 如果链底深度小于链顶,就说明两条链没有 ...