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

笔者使用的是 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

时间: 2024-10-13 06:58:41

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

使用渲染纹理的制作摄像头

https://www.cnblogs.com/chenliyang/p/6558455.html 使用渲染纹理的制作摄像头 从Unity5版本开始,渲染纹理的功能对所有用户开放(是的,包括免费版本)!你可以使用此功能来创造很酷的效果,在本文中,我将向你说明怎么创建一个很酷的监控相机,把相机视图投影到屏幕上. 什么是渲染纹理呢? 渲染纹理是一种你可以在其上绘制,然后像使用其他精灵/纹理一样的纹理.其中,最酷的一件事就是你可以使用它作为相机视图的目标--这样相机把它所见的绘制到纹理上(而不是显示在

Unity Shader 知识点总结(二)

紧接着上一篇文章的shader入门知识的总结,本文主要总结shader中的纹理贴图.透明度混合.顶点动画.后期特效处理等操作.如果有什么地方有错,请指出更正,谢谢.本文的代码主要来自开源书:unity入门精要 一.Unity shader中的纹理 1.简单纹理 在unity shader中,纹理的主要作用是用来给模型贴上一个外表,这样得到的模型颜色就具有纹理的颜色混合.在常见的一些shader上,都会有一个_MainTex的选项,这就是我们常常用的主纹理贴图.对于纹理贴图,其对应的需要有纹理坐标

Unity Shader入门教程(一)

参考文献:http://www.360doc.com/content/13/0923/15/12282510_316492286.shtml Unity Shader是着色器,将纹理.网格信息输入,得到材质的一段程序,具体是个什么东西,还需要亲自实践才知道.一个Unity大神推荐我:如果要学计算机图形编程(游戏编程的基础),可以先学习UnityShader,往后再学习OpenGL和DX.不说废话,依我的风格,都是直接看实例,笔者的教程偏向于傻瓜式的,应该适合入门. 前提:安装了Unity和VS,

Unity Shader (9)

1.物理渲染中重要的一项高光反射因子,菲涅尔效果 1)Schlick菲涅尔近似等式,F0是反射系数,用于控制菲涅尔反射强度,v是视角方向,n代表表面法线 2)Empricial菲涅尔近似等式,bias.scale.power等都是控制项 2.现在的GPU允许间其渲染结果输出到中间缓冲--渲染目标纹理(Render Target Texture,RTT) 1)创建纹理类型--渲染纹理(Render Texture)的文件,赋值给需要输出RTT的摄像机的Target Texture属性,目标输出内容

【Unity Shader】(八) ------ 高级纹理(上)

笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题.    [Unity Shader](三) ------ 光照模型原理及漫反射和高光反射的实现    [Unity Shader](四) ------ 纹理之法线纹理.单张纹理及遮罩纹理的实现    [Unity Shader](五) ------ 透明效果之半透明效果的实现及原理    [Unity Shader](六) ------ 复杂的光

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

笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题. [Unity Shader](三) ------ 光照模型原理及漫反射和高光反射的实现 [Unity Shader](五) ------ 透明效果之半透明效果的实现及原理 在游戏中,我们除了能看到游戏物体的形体轮廓,还能看到物体的一些具体外观,包括颜色,凹凸等.而实现这一步的就是使用 纹理.与纹理相对应的技术就是 纹理映射技术 ,相当于把一张图

Unity Shader 卡通渲染 基于退化四边形的实时描边

一.边缘检测算法 3D模型描边有两种方式,一种是基于图像,即在所有3D模型渲染完成一张图片后,对这张图片进行边缘检测,最后得出描边效果.一种是基于空间,即针对3D模型的三角面三个顶点构成的线条做边缘检测(注:和基于图像的边缘检测的检测方式是不同的,但都叫边缘检测).在本文中使用的是基于空间的3D模型的描边. 空间的3D模型的描边的边是有类型的,大致总结有4种:轮廓边.边界边.折缝边.材质边.其中前3种是本文认为卡通渲染所必须有的. 图1 空间中的边分类 来自文献[1] 轮廓边的检测根据定义就是,

[Unity Shader笔记]渲染路径--Forward渲染路径

[Unity Shader笔记]渲染路径--Forward渲染路径 (2014-04-22 20:08:25) 转载▼ 标签: shader unity renderingpath forward 游戏 分类: UnityShader *ForwardBase.ForwardAdd的LightMode只能运行在Came为Forward.DeferredLighting的渲染模式下 *ForwardAdd这个Pass需要和ForwardBase一起使用,否则会被Unity忽视掉 * Forward

从零开始Unity3D游戏开发【4 材质球和渲染纹理】

[创建材质球] 1.Project 面板下  create-Material 然后将材质球拖放到物体,物体的颜色便会和材质球相同: [渲染纹理 RenderTexture]