笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题。

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的实现
【Unity Shader】(四) ------ 纹理之法线纹理、单张纹理及遮罩纹理的实现
【Unity Shader】(五) ------ 透明效果之半透明效果的实现及原理
【Unity Shader】(六) ------ 复杂的光照(上)
【Unity Shader】(七) ------ 复杂的光照(下)
【Unity Shader】(八) ------ 高级纹理之立方体纹理及光线反射、折射的实现

前言

本文承接前文 【Unity Shader】(八) ------ 高级纹理(上),介绍另外一种高级纹理:渲染纹理及一些相关应用。建议读者先翻看前文再阅读本文会更容易理解。

一. 渲染纹理

渲染纹理是本文的重点介绍对象。如果你使用过 RenderTexture 来实现一些特殊的效果,那么你会更能理解本文的内容。

1.1 什么是渲染纹理

在笔者以前的博文中介绍了许多概念,其中大多提到了 缓冲(buffer)这个名词 ,在之前我们实现的效果中,都是将摄像机的渲染效果输出到颜色缓冲中,然后显示到屏幕上。GPU 允许我们将渲染结果输出到一个中间缓冲,称为渲染目标纹理。

根据官方的定义,我们可知,渲染纹理是一种可以实时更新的特殊纹理,同时我们也可以将它像普通纹理一样应用于一个材质中。那么我们如何创建一个渲染纹理呢?通常我们会使用以下两种方法来创建一个渲染纹理:

  • 在 Project 下右键创建
  • 利用 GrabPass 或者 OnRenderImage 来获取当前屏幕图像(OnRenderImage 函数是我们实现屏幕特效的核心方法之一,所以我不打算在此处进行介绍)

通过以上的方法我们就可以创建出一个渲染纹理了,那么我们来利用它实现一些效果。

二. Mirror

先来看看我们要实现的效果

可以看到场景中有一面区域可以镜像映射场景中的事物图像,这就是我们要实现的类似镜子的效果。那么现在我们开始实现它。

2.1 准备工作

(1)创建一个场景,其中为了观察效果,我使用了前文实现的立方体纹理来作为天空盒。

(2)创建 2 个 Cube,2 个 Sphere,分别赋予不同的颜色用于区别。当然你可以放上你喜欢的模型。

(3)创建一个 Quad ,将 Quad 的位置放在步骤创建的 Cube 和 Sphere 前面,面向 Cube 和 Sphere 。

(4)创建一个 Material 和 一个 RenderTexture ,命名为 Mirror 。将 RenderTexture 赋予材质,将材质赋予 Quad 。

   

(5)创建一个摄像机,调整位置,视野,使其相当于 Quad 望向于 Cube 和 Spere,将 RenderTexture 赋予摄像机的 Target Texture。

   

(6)先观察一下效果。

可以看到 Quad 的确有点像一面镜子一样,但有一点十分诡异。没错,那就是物体位置在 X 轴上相反了

前面说过,我们调整摄像机,让其相当于望向物体,那么它的视野应该是这样的

  

  

如果不做什么修改,直接把 RenderTexture 赋予 Quad,那么 Quad 上的图像就是这样的,很显然不符合我们的思维习惯

  

(7)因为镜子是镜像的,所以我们要解决步骤 6 中出现的问题,创建一个 shader 命名为 Mirror,实现以下的效果。

   

2.2 实现 shader

要解决上述问题其实在思路上是比较简单的,只需要进行 X 轴(水平方向上的翻转)就可以了,只是涉及了 UV 和纹理采样的操作,且不用计算光照等,所以这个 shader 是比较简单的。

I. 定义 Properties 块

我们在 Properties 中只需要一个纹理属性,对应着前面创建的 RenderTexture 。

II. 定义输入输出结构体

III.接下来就是在顶点着色器中翻转 UV 的 x 分量,然后在片元着色器中利用翻转过后的 UV 来对 RenderTexture 采样

完整代码:

 1 Shader "Unity/RenderTexture/Mirror" {
 2     Properties {
 3
 4         _MainTex ("Albedo (RGB)", 2D) = "white" {}
 5
 6     }
 7     SubShader
 8     {
 9         Pass
10         {
11             CGPROGRAM
12             #pragma vertex vert
13             #pragma fragment frag
14             #include "UnityCG.cginc"
15
16             sampler2D _MainTex;
17             struct a2v
18             {
19                 fixed4 vertex : POSITION;
20                 fixed4 texcoord : TEXCOORD0;
21             };
22
23             struct v2f
24             {
25                 fixed4 pos : SV_POSITION;
26                 fixed4 uv : TEXCOORD0;
27             };
28
29             v2f vert(a2v v)
30             {
31                 v2f o;
32                 o.pos = UnityObjectToClipPos(v.vertex);
33                 o.uv = v.texcoord;
34                 o.uv.x = 1 - o.uv.x;
35                 return o;
36             }
37
38             fixed4 frag(v2f i) : SV_Target
39             {
40                 return tex2D(_MainTex,i.uv);
41             }
42
43
44
45             ENDCG
46
47         }
48
49     }
50     FallBack Off
51 }

IV.关闭 FallBack,保存回到 Unity,查看效果

可以看到镜子确实翻转了。

当我们移动物体的时候

可以看到镜子有实时地映射出图像

三. Glass

介绍完了镜子效果,我们接着来介绍另外一个与镜子相关的物体,玻璃。玻璃绝对是很常见的一种效果,而我们实现这种效果的时候正好可以介绍前文所说的使用 GrabPass 抓取屏幕图像的方法。我们先来看一下官方文档对其的定义。

3.1 GrabPass

ShaderLab: GrabPass

GrabPass is a special pass type - it grabs the contents of the screen where the object is about to be drawn into a texture. This texture can be used in subsequent passes to do advanced image based effects.

可以看到 GrabPass 是一种特殊的 Pass ,它可以抓取屏幕中要将对象绘制到纹理中的内容,而且抓取到的纹理可以在其他 Pass 中使用。而它的使用方法如下:

GrabPass 和我们之前使用的 Pass 一样,写在 SubShader 中,同样可以使用 Name 和 Tag 的命令。它有两种使用方法

  • GrabPass {} ,这种方法抓取时,后续的 Pass 可以通过 _GrabTexture 来访问屏幕图像,要注意的是,对于为一个使用它的物体,Unity 都会为其单独进行一次抓取操作。这样每个物体都可以得到不同的屏幕图像,这取决于这个物体的渲染顺序及当前屏幕的缓冲颜色。当然,这样会造成不小的性能消耗。

  • GrabPass { “TextureName” } ,指定一张纹理,抓取的屏幕图像会存储到这张纹理中,而后续的 Pass 可以访问这张纹理来访问屏幕图像。这种方法抓取屏幕时,Unity 只会在每一帧为第一个使用这张纹理的物体执行一次抓取屏幕的操作。所以,如果场景中有复数个物体使用了这张纹理,那么它们得到的屏幕图像其实是一样的,且为第一个使用这张纹理的物体得到的屏幕图像。

3.2 准备工作

(1)创建一个 Cube 和 一个 Sphere,将 Sphere 放置在 Cube 中心。

(2)创建一个 Material 和 一个 shader,命名为 Glass,将 Material 赋予 Cube。

(3)修改 shader。

3.3 实现玻璃 shader

先从我们的需求出发,整理思路。我们要实现的是一个玻璃的效果,那么玻璃必定涉及光线的反射和折射,所以我们要计算光照;同时玻璃是透明的,我们要注意渲染顺序;一般而言,玻璃也有不少是花纹的,所以也涉及纹理采样的操作;而且我们还要抓取屏幕。综合起来,大概如下

  • 计算光照
  • 纹理采样
  • 透明物体的处理
  • 屏幕抓取

上面就是我们需要注意的主要的几个板块,那么现在我们开始实现这个 shader。

I. 定义 Properties 块

一般也有许多玻璃是带纹理的,所以这里也定义了普通纹理和法线纹理的属性,同时还有天空盒的属性,至于用不用就看实际情况了。_Distortion 表示光线折射时的扭曲程度,_RefractAmount 为 0 时,只含反射效果,_RefractAmount 为 1 时,只含折射效果。

II. 定义渲染队列,且抓取屏幕

因为玻璃是透明物体,所以渲染队列设置为 Transparent ,而后面的渲染状态的设置读者可能会感到奇怪,这里先不提,在后面的学习中,我们还会看到这个问题的。而在 GrabPass 中,我们指定了一个纹理 _RefractionTex。

III. 定义相匹配的属性

这里需要注意的是,我们定义了 _RefractionTex,为了在其它 Pass 中通过它来访问屏幕图像,同时 _RefractionTex_TexelSize 表示纹理的纹素大小,对屏幕图像采样时使用。

IV.接着定义输入输出结构体

这里需要注意的是,我们要将法线方向从切线空间转换到世界空间中,所以我们要构造一个转换矩阵。而输出结构体中,screen 代表我们要对被抓取的屏幕图像的采样坐标,TtoW0,TtoW1,TtoW2 则用于构建转换矩阵。

V.定义顶点着色器

顶点着色器和片元着色器是最重要的两个部分。这里我们分步骤来解释这里的操作

(1)先对顶点坐标进行空间转换。

(2)利用 Unity 内置函数 ComputeGrabScreenPos 得到对应抓取屏幕图像的采样坐标。我们可以在 UnityCG.cginc 中看到它的定义

(3)然后对纹理采样,如果对纹理的相关操作不熟悉的读者,可以翻看 【Unity Shader】(四) ------ 纹理之法线纹理、单张纹理及遮罩纹理的实现 这篇博文。

(4)最后构建对应此顶点的转换矩阵,实际上该矩阵是 3 x 3 的矩阵,而定义成 4 维变量则是为了利用 w 分量来存储世界空间的顶点坐标。

VI.定义片元着色器

(1)在顶点着色器中,我们利用 TtoW0,TtoW1,TtoW2 的 w 分量来存储世界空间下的顶点坐标,现在我们直接把它抽出来即可

(2)计算视角方向

(3)利用内置函数 UnpackNormal 得到切线空间下法线方向

(4)计算真正的屏幕坐标,然后采样,得到模拟的折射颜色。对这个算法感到疑惑的读者,可以去查阅一下透视除法

(5)分别利用 TtoW0,TtoW1,TtoW2 和上面得到的切线空间下的法线方向做点乘,就可以得到世界空间下的法线方向

(6)利用得到的新的法线方向来计算反射方向

(7)对主纹理采样

(8)对环境映射进行采样,得到反射颜色

(9)在计算最终颜色的式子中,我们可以看到,如果 _RefractAmount 为 0,那么只有反射颜色,如果 _RefractAmount 为 1,那么只有折射颜色。

VII.完整代码

 1 Shader "Unity/RenderTexture/Glass" {
 2     Properties {
 3
 4         _MainTex ("Main Tex", 2D) = "white" {}
 5         _BumpMap ("Normal Map",2D) = "bump" {}
 6         _CubeMap ("Environment CubeMap",Cube) = "_Skybox"{}
 7         _Distortion ("Distortion",Range(0,100)) = 10
 8         _RefractAmount ("Refract Amount",Range(0.0,1.0)) = 1.0
 9
10
11     }
12     SubShader
13     {
14         Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }
15         GrabPass { "_RefractionTex" }
16         Pass
17         {
18             CGPROGRAM
19             #pragma vertex vert
20             #pragma fragment frag
21             #include "UnityCG.cginc"
22
23             sampler2D _MainTex;
24             float4 _MainTex_ST;
25             sampler2D _BumpMap;
26             float4 _BumpMap_ST;
27             samplerCUBE _CubeMap;
28             float _Distortion;
29             float _RefractAmount;
30             sampler2D _RefractionTex;
31             float4 _RefractionTex_TexelSize;
32
33             struct a2v
34             {
35                 float4 vertex : POSITION;
36                 float3 normal : NORMAL;
37                 float4 tangent : TANGENT;
38                 fixed4 texcoord : TEXCOORD1;
39             };
40
41             struct v2f
42             {
43                 float4 pos : SV_POSITION;
44                 float4 screen : TEXCOORD0;
45                 fixed4 uv : TEXCOORD1;
46                 float4 TtoW0 : TEXCOORD2;
47                 float4 TtoW1 : TEXCOORD3;
48                 float4 TtoW2 : TEXCOORD4;
49             };
50
51             v2f vert(a2v v)
52             {
53                 v2f o;
54                 o.pos = UnityObjectToClipPos(v.vertex);
55                 o.screen = ComputeGrabScreenPos(o.pos);
56                 o.uv.xy = TRANSFORM_TEX(v.texcoord,_MainTex);
57                 o.uv.zw = TRANSFORM_TEX(v.texcoord,_BumpMap);
58
59                 float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
60                 fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
61                 fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
62                 fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
63
64                 o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
65                 o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
66                 o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
67 ;                return o;
68             }
69
70             fixed4 frag(v2f i) : SV_Target
71             {
72                 float3 worldPos = float3(i.TtoW0.w,i.TtoW1.w,i.TtoW2.w);
73                 fixed3 worldViewDir = UnityWorldSpaceViewDir(worldPos);
74
75                 fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
76
77                 float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
78                 i.screen.xy = offset * i.screen.z + i.screen.xy;
79                 fixed3 refrCol = tex2D(_RefractionTex, i.screen.xy / i.screen.w).rgb;
80
81                 bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
82                 fixed3 reflDir = reflect(-worldViewDir, bump);
83                 fixed4 texColor = tex2D(_MainTex, i.uv.xy);
84                 fixed3 reflCol = texCUBE(_CubeMap, reflDir).rgb * texColor.rgb;
85
86                 fixed3 finalColor = reflCol * (1 - _RefractAmount) + refrCol * _RefractAmount;
87
88                 return fixed4(finalColor, 1);
89             }
90
91
92
93             ENDCG
94
95         }
96
97     }
98     FallBack "Diffuse"
99 }

VIII.保存,回到 Unity ,查看效果

上图均是 _RefractAmount 为 0.75 的效果。希望读者能够动手实现一下,这样才能比图片更能感受到这个效果。

四. 总结

渲染纹理是十分常用的高级纹理,我们常常用它来实现一些十分精美的效果,除此之外,还有一种程序纹理。程序纹理是指由计算机生成的图像,这些图像可以做到十分的真实及丰富,不过笔者并没有学习过相关知识,所以就不误人子弟了。

在实现玻璃效果的 shader 中,涉及了各方面的操作,整体上还是有点复杂的,如果读者感到吃力或完全看不懂,那我希望读者去翻看一下前面的知识点,包括纹理采样,光线反射,折射这些现象的原理及实现方法。最后,希望本文能对您有所帮助。

转载于:https://www.cnblogs.com/BFXYMY/p/9947763.html

【Unity Shader】(九) ------ 高级纹理之渲染纹理及镜子与玻璃效果的实现相关推荐

  1. (十六)unity shader之——————高级纹理之渲染纹理(镜子、玻璃效果)

    在之前的学习中,一个摄像机的渲染结果会输出到颜色缓冲中,并显示到我们的屏幕上.现在的GPU允许我们把整个三维场景渲染到一个中间缓冲中,即渲染目标纹理(Render Target Texture,RTT ...

  2. Unity Shader 之 简单实现折叠平面(翻书)的效果

    Unity Shader 之 简单实现折叠平面(翻书)的效果 目录 Unity Shader 之 简单实现折叠平面(翻书)的效果 一.简单介绍 二.实现原理 三.注意事项 四.效果预览 五.实现步骤 ...

  3. 【Unity Shader入门精要】普通纹理和高级纹理

    原作者博客链接:http://blog.csdn.net/candycat1992/article/  书籍链接:http://product.dangdang.com/23972910.html 纹 ...

  4. Unity shader Note :高级纹理(CubeMap反射折射菲涅尔,Rendermap镜子玻璃,程序纹理)

    1.Cubemap–反射折射 使用脚本来创建Cubemap ①通过Camera.RenderToCubemap 把任意位置观察到的场景图制作成一张Cubemap之中 ②脚本使用自定义编译窗体的命令 – ...

  5. (十七)unity shader之——————高级纹理之程序纹理

    程序纹理指的是由那些计算机生成的图像,我们通常使用一些特定的算法来创建个性化图案或非常真实的自然元素,例如木头.石子等.使用程序纹理的好处在于我们可以使用各种参数来控制纹理的外观,而这些属性不仅仅是那 ...

  6. (十九)unity shader之——————基于物理的渲染技术(PBS):中篇(Unity 5中的Standard Shader的实现和使用)

    一.unity 5中的standard shader 在unity5中新创建一个模型或是新创建一个材质时,默认使用的着色器都是一个名为standard 的着色器.这个standard shader使用 ...

  7. (二十)unity shader之——————基于物理的渲染技术(PBS):下篇(PBS技术拓展:全局光照、伽马校正、HDR)

    前面两篇文章我们介绍了PBS实现的数学和理论基础,和standard shader的原理和实现,还有一些其他的渲染相关的unity技术.其中有些概念和技术没有讲的很详细,现在对这些重要的概念进行更深入 ...

  8. 【unity shader】高级光照 --- 薄膜干涉

    -光照模型是shader编程的核心与基础. 一般的光照模型–不管是lambert还是phong–其实都是对现实光照的模拟. 但是现实中的光照效果要复杂得多.但就光的反射而言, 薄膜干涉就是一种非常常见 ...

  9. Unity Shader 实现简单的宝石渲染

    入职实习的第一周过去了,感觉还是相当不错的,找到工作了也就不用再顶着高压去学习了,快乐. 闲话少说,先上效果图啦. 效果还是很棒的,但这不是我写的,哈哈,这个代码可以完全说就是抄Unity的案例.As ...

  10. unity shader 后处理实现水墨风格渲染「Low Poly 」变「水墨画 」

    #水墨风格渲染 这次学校的比赛打算做一个中国古代背景的游戏,所以尝试做了水墨风格的渲染. 主要按以下四步来实现的效果: 根据色调和饱和度调整饱和度. 对图像进行模糊 水墨风格的物体边缘 物体内画笔笔触 ...

最新文章

  1. oracle10g重新配置crs,Oracle 10G RAC一节点系统重做后修复
  2. 从今天开始,学习Webpack,减少对脚手架的依赖(下)
  3. 手把手教你实现一个 AdaBoost
  4. swift-UITextfield控件的基本属性设置
  5. 操作系统 ——进程的状态与转换
  6. 英伟达3080Ti、3070Ti来了!
  7. 第2章[2.4] Ext JS的类与类体系
  8. (可行方案)mysql5.7.9安装教程
  9. 递归存储过程中使用cursor
  10. python类和对象_Python类和对象
  11. python的基础语法
  12. EXCEL表格-利用随机函数制作抽签工具(RAND)
  13. 第21课: JSP语句 if判断语句 (JSP教程 JSP入门实战教程 黄菊华Java网站开发系列教程)
  14. 2020民泰银行软件测试,2020应届生银行工作一个半月感受
  15. 基于C++的数据结构-1
  16. MySQL面试题经典40问!(全)
  17. iOS 可用的热更新、热修复方案
  18. 蚂蚁区块链正式升级为蚂蚁链,究竟在下怎样的一盘大棋?
  19. 入侵检测系统IDS工作原理笔记
  20. 2021全球人才竞争力指数排名:瑞士居首,中国跻身前40;德科集团与北京外企成立合资公司LHH FESCO | 美通社头条...

热门文章

  1. go语言中金币分配训练--pm
  2. 待更新内容mongodb
  3. 抛开百度、知乎等都找不到连接不上服务器远程桌面的原因
  4. java蘑菇岛种子_我的世界:Java版玩家笑开了怀,4个地图种子,2个内陆蘑菇岛...
  5. java 位与 取模_【Java基础】14、位与()操作与快速取模
  6. python能print中文吗_python用print输出中文字符
  7. 《波斯王子-时之砂》精美图文攻略
  8. 错过现场不要紧,数据智能技术论坛文章、视频大回顾
  9. VMware Converter迁移linux系统虚拟机
  10. P1969 积木大赛