在PostImage中经常会用到物体本身的位置信息,但是Image Effect自身是不包含这些信息的,因为屏幕后处其实是使用特定的材质渲染一个刚好填满屏幕的四边形面片(四个角对应近剪裁面的四个角)。这篇文章主要介绍几种在Image Effct shader中还原世界坐标的方式。这个问题在《Shader入门精要》中也做了描述,这里可能偏重于个人的一些疑惑。

这篇文章相关的两外两篇文章:

Unity Shader 基础(3) 获取深度纹理

Unity Shader 基础(2) Image Effect

1. View-Projection 逆矩阵

虽然在Image Effect中没有实际的顶点信息,但是带有纹理uv'坐标信息以及获得深度信息,根据UV信息可以得知NDC下xy坐标,即:

\[x_{ndc} = 2 * uv.x -1 \\ y_{ndc} = 2 * uv.y - 1
\]

在加上通过深度纹理或者深度法线纹理,可以获得NDC下深度信息,从而可以计算出世界坐标。

\[P_world = {M_{view}}^{-1} * {M_{projection}}^{-1} * P_{ndc}
\]

详情可可以参考:Unity Answer:Reconstructing world pos from depth 以及GPU Gem3 :Chapter 27. Motion Blur as a Post-Processing Effect

Pixel shader代码*,逆矩阵需要从C#中传递过来:

fixed4 frag(uoutput o) : COLOR
{
fixed4 col = tex2D(_MainTex, o.uv);
float depth = UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, o.uv_depth));
float4 ndcPos = float4(o.uv.x* 2 - 1 ,o.uv.y * 2 - 1 ,depth , 1); //_Matrix_vp_inverse外部传递,具体为:
//Matrix4x4 temp = mCam.projectionMatrix * mCam.worldToCameraMatrix;
//temp = temp.inverse;
//mMat.SetMatrix("_Matrix_vp_inverse", temp); float4 worldHPos = mul(_Matrix_vp_inverse,ndcPos);
float4 worldPos = worldHPos / worldHPos.w;
float dis = length(worldPos.xyz);
float3 worldPos2 = worldPos.xyz/dis;
worldPos2 = worldPos2 * 0.5 + 0.5;
return fixed4(worldPos2,1);
}

2 远剪裁面插值

理解一下两点

  1. Image Effect是Post Processing 的一种方式,大致过程就是把Color Buffer的输出当做纹理,然后采用特性的材质渲染和屏大小一样的四角形面片(面片的四角即近剪裁面的四个角)。
  2. Vertex shader输出的数据到Pixel shader输入,经过光栅化会进行插值。远剪裁面的四条射线在Pixel shader后是经过插值的,如下图:



    基于上面两点: 对从摄像机原点出发,经剪裁面的摄像机射线进行插值获得每个位置的摄像机视线方向信息,再已知深度信息的情况下即可 获得摄像机位置 。

取其中一个射线:



根据图中比例关系:

\[\frac{YellowL}{YellowL + GreenL} = \frac{\vec{BlackV}}{\vec{BlackV} + \vec{BlueV}}
\]

因为\(YellowL + GreenL = 1\) , 所以:

\[\vec{BlackV} = YellowL * ({\vec{BlackV} + \vec{BlueV}} )
\]

其中YellowL为DepthMap中01空间数值,${\vec{BlackV} + \vec{BlueV}} \(为远剪裁面四角向量插值后向量\)\vec{interpolatedRay}$。

\[wPos = camWPos + \vec{BlackV} = camWPos + YellowL * \vec{interpolatedRay}
\]

实现过程中:Vertex Shader中计算射线向量,Pixel shader中插值计算结果

struct uoutput
{
float4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
float4 uv_depth : TEXCOORD1;
float4 interpolatedRay : TEXCOORD2;
float3 cameraToFarPlane : TEXCOORD3; }; uoutput far_ray_vert(uinput i)
{
uoutput o;
o.pos = mul(UNITY_MATRIX_MVP, i.pos);
//o.uv = MultiplyUV(UNITY_MATRIX_TEXTURE0, i.uv);
o.uv = i.uv ;
o.uv_depth.xy = o.uv ;
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
o.uv_depth.y = 1 - o.uv_depth.y;
#endif // 计算远剪裁面每个角相对摄像机的向量
// Clip space X and Y coords
float2 clipXY = o.pos.xy / o.pos.w; // Position of the far plane in clip space
float4 farPlaneClip = float4(clipXY, 1, 1); // Homogeneous world position on the far plane
farPlaneClip *= float4(1,_ProjectionParams.x,1,1);
float4 farPlaneWorld4 = mul(_ClipToWorld, farPlaneClip); // World position on the far plane ?????
float3 farPlaneWorld = farPlaneWorld4.xyz / farPlaneWorld4.w; // Vector from the camera to the far plane
o.cameraToFarPlane = farPlaneWorld - _WorldSpaceCameraPos; return o;
} fixed4 far_ray_frag(uoutput o) : COLOR
{
float linearDepth = Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, o.uv_depth)); float3 worldPos = _WorldSpaceCameraPos + linearDepth * o.cameraToFarPlane; //颜色输出
float dis = length(worldPos.xyz);
float3 worldPos2 = worldPos.xyz/dis;
worldPos2 = worldPos2 * 0.5 + 0.5;
return fixed4(worldPos2,1);
}

C#代码:

mMat.SetMatrix("_ClipToWorld", (mCam.cameraToWorldMatrix * mCam.projectionMatrix).inverse);

3. 近剪裁面射线插值

原理上上面类似,实际推导过程,可以参考《Shader 入门精要》或者这里, 代码下载:下载

计算近近剪裁面四个角相对摄像机向量

	Matrix4x4 GetFrustumCorners()
{
Matrix4x4 frustumCorners = Matrix4x4.identity;
Camera camera = mCam;
Transform cameraTransform = mCam.gameObject.transform; float fov = camera.fieldOfView;
float near = camera.nearClipPlane;
float aspect = camera.aspect; float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
Vector3 toRight = cameraTransform.right * halfHeight * aspect;
Vector3 toTop = cameraTransform.up * halfHeight; Vector3 topLeft = cameraTransform.forward * near + toTop - toRight;
float scale = topLeft.magnitude / near; topLeft.Normalize();
topLeft *= scale; Vector3 topRight = cameraTransform.forward * near + toRight + toTop;
topRight.Normalize();
topRight *= scale; Vector3 bottomLeft = cameraTransform.forward * near - toTop - toRight;
bottomLeft.Normalize();
bottomLeft *= scale; Vector3 bottomRight = cameraTransform.forward * near + toRight - toTop;
bottomRight.Normalize();
bottomRight *= scale; frustumCorners.SetRow(0, bottomLeft);
frustumCorners.SetRow(1, bottomRight);
frustumCorners.SetRow(2, topRight);
frustumCorners.SetRow(3, topLeft); return frustumCorners;
}
//设置
mMat.SetMatrix("_FrustumCornersWS", GetFrustumCorners());

Shader

 struct uinput
{
float4 pos : POSITION;
half2 uv : TEXCOORD0;
}; struct uoutput
{
float4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
float4 uv_depth : TEXCOORD1;
float4 interpolatedRay : TEXCOORD2;
float4 cameraToFarPlane : TEXCOORD3;
}; uoutput near_ray_vert(uinput i)
{
uoutput o;
o.pos = mul(UNITY_MATRIX_MVP, i.pos);
o.uv = MultiplyUV(UNITY_MATRIX_TEXTURE0, i.uv);
o.uv = i.uv ;
o.uv_depth.xy = o.uv ;
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
o.uv_depth.y = 1 - o.uv_depth.y;
#endif int index = 0;
if (i.uv.x < 0.5 && i.uv.y < 0.5)
{
index = 0;
}
else if (i.uv.x > 0.5 && i.uv.y < 0.5)
{
index = 1;
}
else if (i.uv.x > 0.5 && i.uv.y > 0.5)
{
index = 2;
}
else
{
index = 3;
}
o.interpolatedRay = _FrustumCornersWS[(int)index];
return o;
}
fixed4 near_ray_frag(uoutput o) : COLOR
{
float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, o.uv_depth));
float3 worldPos = _WorldSpaceCameraPos + linearDepth * o.interpolatedRay.xyz;
return WorldPosTo01(worldPos); float dis = length(worldPos.xyz);
float3 worldPos2 = worldPos.xyz/dis;
worldPos2 = worldPos2 * 0.5 + 0.5;
return fixed4(worldPos2,1);
}

4. 小结

上面的推到过程,参考到一些<shader入门精要>以及Jim的博客,在Unity提供的Effect(Global Fog以及Emotion Blur)中也有使用类似的方式。对Global Fog中插值使用方式还挺有点不甚理解,使用pos.z作为射线索引,没弄明白这个Z为啥可以作为索引。

文章源码测试源码下载:http://pan.baidu.com/s/1c2rHVf6

Unity Shader 基础(4) 由深度纹理重建坐标的更多相关文章

  1. Unity Shader 基础(3) 获取深度纹理

    Unity提供了很多Image Effect效果,包含Global Fog.DOF.Boom.Blur.Edge Detection等等,这些效果里面都会使用到摄像机深度或者根据深度还原世界坐标实现各 ...

  2. Unity Shader基础

    Unity Shader基础 先上代码,代码一般是这样的. void Initialization(){ //先从硬盘加载代码再加载到GPU中 string vertexShaderCode = Lo ...

  3. Unity Shader入门精要学习笔记 - 第3章 Unity Shader 基础

    来源作者:candycat   http://blog.csdn.net/candycat1992/article/ 概述 总体来说,在Unity中我们需要配合使用材质和Unity Shader才能达 ...

  4. 第二章 Unity Shader基础

    [TOC] 1. Unity Shader 的基础: ShaderLab 学习和编写着色器的过程一直是一个学习曲线很陡峭的过程,通常情况下为了自定义渲染效果往往要和很多文件和设置打交道,这些设置很容易 ...

  5. Unity Shader 基础

    推荐: https://www.cnblogs.com/nanwei/p/7277417.html 上面链接作者的整个系列都写的不错 https://www.cnblogs.com/nanwei/ca ...

  6. 【Unity Shader】(四) ------ 纹理之法线纹理、单张纹理及遮罩纹理的实现

    笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题. [Unity Shader](三) ----- ...

  7. Unity Shader 基础(1): RenderType & ReplacementShader

    很多Shader中都会定义RenderType这个类型,但是一直搞不明白到底是干嘛的,官方文档是这样结解释的:Rendering with Replaced Shaders Rendering wit ...

  8. Unity Shader基础(1):基础

    一.Shaderlab语法 1.给Shader起名字 Shader "Custom/MyShader" 这个名称会出现在材质选择使用的下拉列表里 2. Properties (属性 ...

  9. [Unity] Shader(着色器)之纹理贴图

    在Shader中,我们除了可以设定各种光线处理外,还可以增加纹理贴图. 使用 settexture 命令可以为着色器指定纹理. 示例代码: Shader "Sbin/ff2" { ...

随机推荐

  1. 分布式系统监视zabbix讲解三之用户和用户组--技术流ken

    概述 Zabbix 中的所有用户都通过 Web 前端去访问 Zabbix 应用程序.并为每个用户分配唯一的登陆名和密码. 所有用户的密码都被加密并储存于 Zabbix 数据库中.用户不能使用其用户名和 ...

  2. C#异常处理。

    一.什么是异常? 程序运行时发生的错误. 二.异常处理的一般代码模式. try{..可能发生异常的代码} catch{..对异常的处理} finally{...无论是否发生异常.是否捕获异常都会执行的 ...

  3. nginx静态资源文件无法访问,403 forbidden错误

    在安装 nginx 服务器后,我想把网站的根目录设置为 /root/www/ ,于是对 nginx 的 nginx.conf 文件进行配置 先打开 nginx.conf #user nobody; w ...

  4. [android] logcat简介

    /****************2016年5月4日 更新**************************/ 知乎:Android中的LogCat为什么叫作LogCat? 刘贺: linux有个命 ...

  5. js对HTML字符转义与反转义

    注意: 在编写html时,经常需要转义,才能正常显示在页面上. 并且,还可以防止xss. 解决方案: 一, 使用正则: 使用正则转码: var value = document.getElementB ...

  6. phpstudy 产生You don't have permission to access / on this server.解决

    phpstudy配置好访问目录时候有时候会产生You don't have permission to access / on this server. 解决办法: 修改服务器httpd.conf配置 ...

  7. CF607B Zuma(区间dp)

    题意 题目链接 Sol 裸的区间dp,转移的时候判一下两个字符是否相等即可 #include<bits/stdc++.h> #define Pair pair<int, int> ...

  8. 洛谷P4591 [TJOI2018]碱基序列(hash dp)

    题意 题目链接 Sol \(f[i][j]\)表示匹配到第\(i\)个串,当前在主串的第\(j\)个位置 转移的时候判断一下是否可行就行了.随便一个能搞字符串匹配的算法都能过 复杂度\(O(|S| K ...

  9. 自动排版工具——XML自动排版生成工具

    ——支持全球化/多语言/符合W3C标准的XML自动排版工具 Boxth XML/XSL Formatter是专为XML数据或其他结构化数据源自动输出排版文件(如: PDF等)而设计的集数据格式化.版式 ...

  10. 通过git上传本地代码到github仓库

    最近呢,武汉天气燥热,在公司没啥事,就自己写了一下小demo. 作为一个菜鸟,只在github上扒过别人的代码,还没自己上传过,就试了一下,遇到了一些坑,记录一下. 前提是电脑上安装了git,没有安装 ...