【Unity Shader】(九) ------ 高级纹理之渲染纹理及镜子与玻璃效果的实现
笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题。
前言
本文承接前文 【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 采样
完整代码:
Shader "Unity/RenderTexture/Mirror" {
Properties { _MainTex ("Albedo (RGB)", 2D) = "white" {} }
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc" sampler2D _MainTex;
struct a2v
{
fixed4 vertex : POSITION;
fixed4 texcoord : TEXCOORD0;
}; struct v2f
{
fixed4 pos : SV_POSITION;
fixed4 uv : TEXCOORD0;
}; v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
o.uv.x = - o.uv.x;
return o;
} fixed4 frag(v2f i) : SV_Target
{
return tex2D(_MainTex,i.uv);
} ENDCG } }
FallBack Off
}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.完整代码
Shader "Unity/RenderTexture/Glass" {
Properties { _MainTex ("Main Tex", 2D) = "white" {}
_BumpMap ("Normal Map",2D) = "bump" {}
_CubeMap ("Environment CubeMap",Cube) = "_Skybox"{}
_Distortion ("Distortion",Range(,)) =
_RefractAmount ("Refract Amount",Range(0.0,1.0)) = 1.0 }
SubShader
{
Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }
GrabPass { "_RefractionTex" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc" sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
samplerCUBE _CubeMap;
float _Distortion;
float _RefractAmount;
sampler2D _RefractionTex;
float4 _RefractionTex_TexelSize; struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
fixed4 texcoord : TEXCOORD1;
}; struct v2f
{
float4 pos : SV_POSITION;
float4 screen : TEXCOORD0;
fixed4 uv : TEXCOORD1;
float4 TtoW0 : TEXCOORD2;
float4 TtoW1 : TEXCOORD3;
float4 TtoW2 : TEXCOORD4;
}; v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.screen = ComputeGrabScreenPos(o.pos);
o.uv.xy = TRANSFORM_TEX(v.texcoord,_MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord,_BumpMap); float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
; return o;
} fixed4 frag(v2f i) : SV_Target
{
float3 worldPos = float3(i.TtoW0.w,i.TtoW1.w,i.TtoW2.w);
fixed3 worldViewDir = UnityWorldSpaceViewDir(worldPos); fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw)); float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
i.screen.xy = offset * i.screen.z + i.screen.xy;
fixed3 refrCol = tex2D(_RefractionTex, i.screen.xy / i.screen.w).rgb; bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
fixed3 reflDir = reflect(-worldViewDir, bump);
fixed4 texColor = tex2D(_MainTex, i.uv.xy);
fixed3 reflCol = texCUBE(_CubeMap, reflDir).rgb * texColor.rgb; fixed3 finalColor = reflCol * ( - _RefractAmount) + refrCol * _RefractAmount; return fixed4(finalColor, );
} ENDCG } }
FallBack "Diffuse"
}VIII.保存,回到 Unity ,查看效果
上图均是 _RefractAmount 为 0.75 的效果。希望读者能够动手实现一下,这样才能比图片更能感受到这个效果。
四. 总结
渲染纹理是十分常用的高级纹理,我们常常用它来实现一些十分精美的效果,除此之外,还有一种程序纹理。程序纹理是指由计算机生成的图像,这些图像可以做到十分的真实及丰富,不过笔者并没有学习过相关知识,所以就不误人子弟了。
在实现玻璃效果的 shader 中,涉及了各方面的操作,整体上还是有点复杂的,如果读者感到吃力或完全看不懂,那我希望读者去翻看一下前面的知识点,包括纹理采样,光线反射,折射这些现象的原理及实现方法。最后,希望本文能对您有所帮助。
【Unity Shader】(九) ------ 高级纹理之渲染纹理及镜子与玻璃效果的实现的更多相关文章
- Unity Shader入门精要学习笔记 - 第10章 高级纹理
转载自 冯乐乐的 <Unity Shader入门精要> 立方体纹理 在图形学中,立方体纹理是环境映射的一种实现方法.环境映射可以模拟物体周围的环境,而使用了环境映射的物体可以看起来像镀了层 ...
- 【Unity Shader】(十) ------ UV动画原理及简易实现
笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题. [Unity Shader](三) ----- ...
- Unity Shader 知识点总结(二)
紧接着上一篇文章的shader入门知识的总结,本文主要总结shader中的纹理贴图.透明度混合.顶点动画.后期特效处理等操作.如果有什么地方有错,请指出更正,谢谢.本文的代码主要来自开源书:unity ...
- Unity Shader入门教程(一)
参考文献:http://www.360doc.com/content/13/0923/15/12282510_316492286.shtml Unity Shader是着色器,将纹理.网格信息输入,得 ...
- 【Unity Shader】(八) ------ 高级纹理之立方体纹理及光线反射、折射的实现
笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题. [Unity Shader](三) -- ...
- Unity Shader入门精要学习笔记 - 第7章 基础纹理
转自 冯乐乐的 <Unity Shader 入门精要> 纹理最初的目的就是使用一张图片来控制模型的外观.使用纹理映射技术,我们可以把一张图“黏”在模型表面,逐纹素地控制模型的颜色. 在美术 ...
- Unity Shader 基础(4) 由深度纹理重建坐标
在PostImage中经常会用到物体本身的位置信息,但是Image Effect自身是不包含这些信息的,因为屏幕后处其实是使用特定的材质渲染一个刚好填满屏幕的四边形面片(四个角对应近剪裁面的四个角). ...
- Unity Shader 基础(3) 获取深度纹理
Unity提供了很多Image Effect效果,包含Global Fog.DOF.Boom.Blur.Edge Detection等等,这些效果里面都会使用到摄像机深度或者根据深度还原世界坐标实现各 ...
- Unity Shader入门精要学习笔记 - 第13章 使用深度和法线纹理
线纹理的代码非常简单,但是我们有必要在这之前首先了解它们背后的实现原理. 深度纹理实际上就是一张渲染纹理,只不过它里面存储的像素值不是颜色值而是一个高精度的深度值.由于被存储在一张纹理中,深度纹理里的 ...
随机推荐
- session过期,拦截ajax请求并跳转登录页面
1.方法一 :1.1使用filter 和ajaxsetup 对ajax进行拦截并跳转登录页面 public void doFilter(ServletRequest request, ServletR ...
- 跟我一起阅读Java源代码之HashMap(一)
最近闲的很,想和大家一起学习并讨论下Java的一些源代码以及其实现的数据结构, 不是什么高水平的东西,有兴趣的随便看看 1. 为什么要用Map,以HashMap为例 很多时候我们有这样的需求,我们需要 ...
- Python之数据库模块安装 MySQLdb
安装,下载地址 安装可能会报错, 1.需要安装VC++,到提示的地址中下载安装即可 2.在下载对应的包版本,如果是win7 64位2.7版本的python,就下载 MySQL_python-1.2.5 ...
- MVC四大筛选器—AuthorizeFilter
在Action的执行中包括两个重要的部分,一个是Action方法本身逻辑代码的执行,第二个就是Action方法的筛选器的执行. MVC4中筛选器都是以AOP(面向方面编程)的方式来设计的,通过对Act ...
- debian 7 终端上无法调出输出法
debian 7 终端konsole上无法调出输出法,无法输入汉字的问题解决方案, export GTK_IM_MODULE=fcitxexport QT_IM_MODULE=fcitxexport ...
- CSS3的新增边框属性
一.CSS3 新增的边框属性 属性 版本 简介 border-image CSS3 设置或检索对象的边框使用图像来填充 border-image-source CSS3 设置或检索对象的边框是否用图像 ...
- BZOJ 1877 晨跑 拆点费用流
题目链接: https://www.lydsy.com/JudgeOnline/problem.php?id=1877 题目大意: Elaxia最近迷恋上了空手道,他为自己设定了一套健身计划,比如俯卧 ...
- jQuery全选反选插件
(function($){ $.fn.check = function(options){ var options = $.extend({ element : "input[name='n ...
- django restframework 序列化
Serialization 序列化 创建表 from django.db import models from pygments.lexers import get_all_lexers from p ...
- java集合(类似python的列表)
一:学习方法 我们在学习一个类的时候,如果他是期其他类的实现类,我们在学习的时候,先学习他的共同的继承类,学习他们共有的方法,在学习他实现类的特殊方法.由共性--->特殊. 二:集合 1.集合和 ...