当前位置: 首页 > news >正文

unity屏幕空间阴影

参考:https://zhuanlan.zhihu.com/p/45679584

实验的原理很简单:
1、从摄像机角度拍摄一张深度图,为cameraDepth
2、从灯光角度拍摄一张深度图,为lightDepth
3、将两个图blit,重建世界坐标点,转换到灯光空间,采样灯光空间的深度,比较,然后判断是否在阴影里,最后生成一个新的图,记作screenspaceShadowTex
4、在绘制物体的时候,用物体的屏幕坐标uv,采样这个screenspaceShadowTex,乘以颜色,即可。

为啥叫做屏幕空间。
其实质和shadowmap类似。唯一的不同是,我们将阴影的结果放在一个图上,在绘制物体的时候,用物体的屏幕坐标uv采样,得到系数即可。

实现:
1、从摄像机角度拍摄一张深度图,为cameraDepth

 _depthCamera.RenderWithShader(shadowCasterMat.shader, "");
Shader "Kingsoft/CustomShadow/Caster" 
{
	SubShader 
	{
		Tags { 			
		    "RenderType" = "Opaque"
		}
		Pass 
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			struct v2f 
			{
				float4 pos : SV_POSITION;
				float2 depth:TEXCOORD0;
			};
		
			v2f vert (appdata_full v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.depth = o.pos.zw;
				return o;
			}
		
			fixed4 frag (v2f i) : COLOR
			{
				float depth = i.depth.x/i.depth.y;
				#if defined(SHADER_API_GLES) || defined(SHADER_API_GLES3)
					depth = depth*0.5 + 0.5; //(-1, 1)-->(0, 1)
				#elif defined (UNITY_REVERSED_Z)
					depth = 1 - depth;       //(1, 0)-->(0, 1)
				#endif
				return depth;
			}
			ENDCG 
		}	
	}
}

2、从灯光角度拍摄一张深度图,为lightDepth

if (!_lightCamera)
{
    _lightCamera = CreateLightCamera();

    _lightCamera.transform.parent = _light.transform;
    _lightCamera.transform.localPosition = Vector3.zero;
    _lightCamera.transform.localRotation = Quaternion.identity;
}

_lightCamera.orthographicSize = orthographicSize;
_lightCamera.nearClipPlane = nearClipPlane;
_lightCamera.farClipPlane = farClipPlane;
_lightCamera.RenderWithShader(shadowCasterMat.shader, "");

3、将两个图blit,重建世界坐标点,转换到灯光空间,采样灯光空间的深度,比较,然后判断是否在阴影里,最后生成一个新的图,记作screenspaceShadowTex

Matrix4x4 projectionMatrix = GL.GetGPUProjectionMatrix(Camera.main.projectionMatrix, false);
Shader.SetGlobalMatrix("_inverseVP", Matrix4x4.Inverse(projectionMatrix * Camera.main.worldToCameraMatrix));

shadowCollectorMat.SetTexture("_CameraDepthTex", depthTexture);
shadowCollectorMat.SetTexture("_LightDepthTex", lightDepthTexture);
Graphics.Blit(depthTexture, screenSpaceShadowTexture, shadowCollectorMat);

Shader.SetGlobalTexture("_ScreenSpceShadowTexture", screenSpaceShadowTexture);

projectionMatrix = GL.GetGPUProjectionMatrix(_lightCamera.projectionMatrix, false);
Shader.SetGlobalMatrix("_WorldToShadow", projectionMatrix * _lightCamera.worldToCameraMatrix);

混合的shader:

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Kingsoft/CustomShadow/Collector" 
{
	Subshader 
	{
		ZTest off 
		Fog { Mode Off }
		Cull back
		Lighting Off
		ZWrite Off
		
		Pass 
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma fragmentoption ARB_precision_hint_fastest
			
			uniform sampler2D _CameraDepthTex;
			uniform sampler2D _LightDepthTex;

			uniform float4x4 _inverseVP;
			uniform float4x4 _WorldToShadow;

			struct Input
			{
				float4 texcoord : TEXCOORD0;
				float4 vertex : POSITION;
			};
			
			struct Output 
			{
				float4 pos : SV_POSITION;
				float2 uv : TEXCOORD0;
			};
			
			Output vert (Input i)
			{
				Output o;
				o.pos = UnityObjectToClipPos (i.vertex);
				o.uv = i.texcoord;
				
				return o;
			}

			fixed4 frag( Output i ) : SV_TARGET
			{
				fixed4 cameraDepth = tex2D(_CameraDepthTex, i.uv);
				half depth_ = cameraDepth.r;
#if defined (SHADER_TARGET_GLSL) 
				depth_ = depth_ * 2 - 1;	 // (0, 1)-->(-1, 1)
#elif defined (UNITY_REVERSED_Z)
				depth_ = 1 - depth_;       // (0, 1)-->(1, 0)
#endif

				// reconstruct world position by depth;
				float4 clipPos;
				clipPos.xy = i.uv * 2 - 1;
				clipPos.z = depth_;
				clipPos.w = 1;

				float4 posWorld = mul(_inverseVP, clipPos);
				posWorld /= posWorld.w;

				half4 shadowCoord = mul(_WorldToShadow, posWorld);

				half2 uv = shadowCoord.xy;
				uv = uv*0.5 + 0.5; //(-1, 1)-->(0, 1)

				half depth = shadowCoord.z / shadowCoord.w;
#if defined(SHADER_API_GLES) || defined(SHADER_API_GLES3)
				depth = depth*0.5 + 0.5; //(-1, 1)-->(0, 1)
#elif defined (UNITY_REVERSED_Z)
				depth = 1 - depth;       //(1, 0)-->(0, 1)
#endif

				half4 col = tex2D(_LightDepthTex, uv);
				half sampleDepth = col.r;

				half shadow = (sampleDepth < depth - 0.05) ? 0.1 : 1;

				return shadow;
			}
			ENDCG
		}
	}
	Fallback off
}

4、在绘制物体的时候,用物体的屏幕坐标uv,采样这个screenspaceShadowTex,乘以颜色,即可。

fixed4 frag (v2f_full i) : COLOR0 
{						
	fixed4 tex = tex2D (_MainTex, i.uv.xy)*_Color;
	i.screenPos.xy = i.screenPos.xy / i.screenPos.w;
	float2 sceneUVs = i.screenPos.xy*0.5 + 0.5;
	#if UNITY_UV_STARTS_AT_TOP
		sceneUVs.y = _ProjectionParams.x < 0 ? 1 - sceneUVs.y : sceneUVs.y;
	#endif

	half shadow = tex2D(_ScreenSpceShadowTexture, sceneUVs).r;
	fixed4 c = tex2D(_MainTex, i.uv.xy) * _Color * shadow;
	return c;
}

ok,效果自然是很差的:
在这里插入图片描述

因为没有做软阴影,但是大致的思想是实现了。

其中矩阵的传递、z反转、y反转等等,这些之前的博客都有涉及,不再展开。

最后的最后,再总结下,什么是屏幕空间阴影。
所谓的屏幕空间阴影,是在绘制物体的时候,使用屏幕坐标空间的uv,采样一个shadow图,然后乘以颜色。
这个shadow图,是怎么来的,是通过两个深度图(摄像机和灯光空间的深度图),通过世界坐标的重建,然后再转换到灯光空间,比较深度值,得到的一张shadow图。
效率怎样呢?内存上,使用了3个rt,gpu上,画3次的全场景的物体。第一次发生在从摄像机角度绘制;第二次发生在从灯光角度绘制;第三次在实际绘制物体上。

相关文章:

  • UNITY_NO_SCREENSPACE_SHADOWS打开时机
  • mixed模式下烘焙shadowmask记录的数据
  • 第七章 数组实验
  • unity_OcclusionMaskSelector
  • unity_ShadowFadeCenterAndType
  • 人月神话阅读笔记02
  • unity 烘焙模式——baked indirect/shadow mask/distance shadowmask/subtractive模式的区别
  • 第七章学习小结
  • unity_worldtransformparams.w
  • vue使用install函数把组件做成插件方便全局调用
  • 地形shader分析
  • 3.HTTP协议详解
  • C# 读取bytes——工具类
  • 三角形插值
  • iOS runLoop 原理多线程 总结 NSTimer优化
  • 网络传输文件的问题
  • SegmentFault for Android 3.0 发布
  • 07.Android之多媒体问题
  • 4个实用的微服务测试策略
  • chrome扩展demo1-小时钟
  • iOS帅气加载动画、通知视图、红包助手、引导页、导航栏、朋友圈、小游戏等效果源码...
  • Spring Boot快速入门(一):Hello Spring Boot
  • Terraform入门 - 3. 变更基础设施
  • Vue 2.3、2.4 知识点小结
  • Vue小说阅读器(仿追书神器)
  • 基于MaxCompute打造轻盈的人人车移动端数据平台
  • 漂亮刷新控件-iOS
  • 如何解决微信端直接跳WAP端
  • 怎样选择前端框架
  • 栈实现走出迷宫(C++)
  • 最简单的无缝轮播
  • LevelDB 入门 —— 全面了解 LevelDB 的功能特性
  • 仓管云——企业云erp功能有哪些?
  • 你学不懂C语言,是因为不懂编写C程序的7个步骤 ...
  • ​LeetCode解法汇总2696. 删除子串后的字符串最小长度
  • ​MPV,汽车产品里一个特殊品类的进化过程
  • #gStore-weekly | gStore最新版本1.0之三角形计数函数的使用
  • #pragma multi_compile #pragma shader_feature
  • (js)循环条件满足时终止循环
  • (Ruby)Ubuntu12.04安装Rails环境
  • (博弈 sg入门)kiki's game -- hdu -- 2147
  • (附源码)php投票系统 毕业设计 121500
  • (附源码)springboot宠物管理系统 毕业设计 121654
  • (全注解开发)学习Spring-MVC的第三天
  • (十七)Flask之大型项目目录结构示例【二扣蓝图】
  • (十一)手动添加用户和文件的特殊权限
  • (四)库存超卖案例实战——优化redis分布式锁
  • (一)Java算法:二分查找
  • (原創) X61用戶,小心你的上蓋!! (NB) (ThinkPad) (X61)
  • (转)visual stdio 书签功能介绍
  • .NET Core6.0 MVC+layui+SqlSugar 简单增删改查
  • .NET delegate 委托 、 Event 事件,接口回调
  • .NET/C# 的字符串暂存池
  • .Net6使用WebSocket与前端进行通信
  • .Net转前端开发-启航篇,如何定制博客园主题