之前一直没有自己实现过阴影,只是概念上有所了解,这次通过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. Codeforces E. High Load(构造)

    题目描述: High Load time limit per test 2 seconds memory limit per test 512 megabytes input standard inp ...

  2. Django常用自段和参数

    本文目录 1 ORM字段 2 ORM字段参数 3 关系字段 4 多对多关联关系的三种方式 5 元信息 6 自定义字段(了解) 回到目录 1 ORM字段 AutoField int自增列,必须填入参数 ...

  3. ZJOI2019赛季回顾

    退役了. NOIP2018 day1没什么好说的. day2开考后看完题:这个T3 TM不是DDP吗? 考前刚学过这东西,还没去写过 当时不知道在想什么,胡了T1 60和T2 50分保底之后就去刚T3 ...

  4. 移动端touch与click区别--移动端开发整理笔记(五)

    移动端用touch还是click? 在移动端开发中,click事件有300ms的延时,由来源于iphone处理双击缩放功能种下的坑.因为用手指在屏幕上快速点击两次,iOS 自带的 Safari 浏览器 ...

  5. 使用plv8+ shortid npm包构建一个短唯一id服务

    plv8 是一个很强大的pg 扩展插件,我们可以直接额使用js 增强sql ,shortid 是一个用来生成短连接id 很方便的类库 因为shortid 是一个npm 模块,我们需要使用一种方法使用r ...

  6. [RN] React Native 自定义 底部 弹出 选择框 实现

    React Native 自定义 底部选择框 实现 效果如图所示: 实现方法: 一.组件封装 CustomAlertDialog.js import React, {Component} from ' ...

  7. hdu5111 树链剖分,主席树

    hdu5111 链接 hdu 思路 先考虑序列上如何解决. 1 3 2 5 4 1 2 4 5 3 这个序列变成 1 2 3 4 5 1 3 5 5 2 是对答案没有影响的(显然). 然后查询操作\( ...

  8. 【AGC009E】Eternal Average

    [AGC009E]Eternal Average 题面 洛谷 题解 神仙题.jpg 我们把操作看成一棵\(k\)叉树,其中每个节点有权值,所有叶子节点(共\(n+m\)个)就是\(0\)或\(1\). ...

  9. 《Attack ML Models - 李宏毅》视频笔记(完结)

    Attack ML Models - 李宏毅 https://www.bilibili.com/video/av47022853 Training的Loss:固定x,修改θ,使y0接近ytrue. N ...

  10. Linux 进程树查看工具 pstree

    pstree 是 Linux 下的一个用于展示进程树结构的工具,类似于 tree 展示目录树一样,可视化地查看进程的继承关系.pstree 工具其实是 PSmisc 工具集的成员之一,PSmisc 工 ...