【Unity Shader】 CubeMap(立方体贴图)_赞美月亮的专栏-程序员宝宝

技术标签: unity  shader  cubemap  Unity Shader初学  

Unity Shader 立方体贴图

一、介绍CubeMap

Cubemap是一个由六个独立的正方形纹理组成的集合,它将多个纹理组合起来映射到一个单一纹理。

基本上说CubeMap包含6个2D纹理,这每个2D纹理是一个立方体(cube)的一个面,也就是说它是一个有贴图的立方体。

CubeMap通常被用来作为具有反射属性物体的反射源。

这里写图片描述
你可能会奇怪这样的立方体有什么用?为什么费事地把6个独立纹理结合为一个单独的纹理,只使用6个各自独立的不行吗?这是因为cubemap有自己特有的属性,可以使用方向向量对它们索引和采样

想象一下,我们有一个1×1×1的单位立方体,有个以原点为起点的方向向量在它的中心。方向向量的大小无关紧要,当方向向量向外延伸时,就会和立方体表面上的相应纹理发生相交,我们可以根据该交点进行采样。
这里写图片描述

Shader中对CubeMap采样

Shader提供了CubeMap的内置类型samplerCube,samplerCube和sampler2D一样,都是贴图,不同的是,需要使用textureCube进行采样,采样的时候需要传递规范化的方向向量而不是uv坐标。

texCUBE(_CubeMap, directionVec);

texCUBE会采样方向向量directionVec在CubeMap上的交点。

如果texCube采样时,传入模型中心到定点的向量,就可以将CubeMap纹理贴到模型上了。

Shader "Hidden/CubemapSampler"
{
    Properties
    {
        _CubeMap("CubeMap", CUBE) = ""{}
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;   
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float4 vertexLocal : TEXCOORD1;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertexLocal = v.vertex;
                o.vertex = UnityObjectToClipPos(v.vertex);

                return o;
            }

            samplerCUBE _CubeMap;
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = texCUBE(_CubeMap, normalize(i.vertexLocal.xyz));
                return col;
            }
            ENDCG
        }
    }
}

这里写图片描述

Unity中如何制作CubeMap

  1. 已经有6张2D纹理,直接创建CubeMap
    Project-Create-Legacy-CubeMap
    这里写图片描述

二、Reflect CubeMap(反射立方体纹理用于环境映射)

在图形学中,立方体纹理(CubeMap)是环境映射(Environment Mapping)的一种实现方法。环境映射可以模拟物体周围IDE环境,而使用了环境映射的物体可以看起来像镀了层金属一样反射出周围的环境。

通常我们使用高光来模拟光滑物体对于点光源的反射效果,更进一步,高反光的物体通常可以在表面反射出周围的物体,这样的效果通过高光贴图就无法实现了,需要通过环境贴图CubeMap来实现。。

环境反射的原理很简单,一个光滑的物体表面可以根据我们观察的不同角度反射出不同位置的环境。即物体表面一点反射的颜色和该点的法线,观察视线和反射视线有关系。

环境映射原理

这里写图片描述

如果我们使用texCube函数对立方体纹理进行采样时,使用视线关于物体顶点法线的反射向量作为采样的方向向量就可以得到反射的效果。

反射方向的计算

这里写图片描述
L为入射光(顶点到光源)的单位法向量,N为顶点的单位法向量,R为反射光的单位法向量,V是观察方向。

R=2(N•L)N-L

推导过程:
L在N方向上的投影是|Lcosθ|=cosθ,那么投影矢量N’=Ncosθ
L+S = N’ = Ncosθ
R = L+2S = L+2(Ncosθ-L) = 2Ncosθ-L

简单计算,或者使用Cg的内置函数reflect(),该函数接受参数向量I和法线N,返回I关于N的反射向量。
float3 R = reflect(I,N);

Unity Shader实现Reflection CubeMap(基于CubeMap的反射效果)

步骤:
- 创建用于环境映射的立方体纹理(反射源)
- 用该立方体纹理作为天空盒子
- 使用worldRef(视线关于物体顶点法线的反射向量)作为采样的方向向量,对CubeMap采样作为物体的表面纹理

如果物体的纹理采集自与天空盒子同一个CubeMap,就会有一种环境映射的感觉。

反射CubeMap的Shader代码

Shader "Hidden/CM       Reflect"
{
    Properties
    {
        _CubeMap("CubeMap", CUBE) = ""{}
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;           
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldRef : TEXCOORD1;
                float3 worldPos : TEXCOORD2;
                float3 worldViewDir : TEXCOORD3;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                o.worldNormal = mul(unity_ObjectToWorld, v.normal);
                o.worldViewDir =  normalize(_WorldSpaceCameraPos.xyz - o.worldPos.xyz);
                o.worldRef = reflect(-o.worldViewDir,normalize(o.worldNormal));
                return o;
            }

            samplerCUBE _CubeMap;
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = texCUBE(_CubeMap, i.worldRef);
                return col;
            }
            ENDCG
        }
    }
}

反射效果展示

这里写图片描述

ps:“假”反射?

这种基于CubeMap反射效果的环境映射,并不是“真”反射,只是“模拟”环境反射,是“假”反射。如果我们改变了环境的天空盒子,物体的纹理并不会跟着改变,且不会反射出环境中的其他物体,只能反射天空盒子。
这里写图片描述

一个非常消耗效率的真反射实现方法

创建一个相机、RenderTexture将RenderTexture赋给相机的Render Target,再将RenderTexture赋给物体material的贴图。
这里写图片描述

三、Refract CubeMap(折射立方体纹理用于环境透视)

透明材质,如水,玻璃,水晶,应该可以透视。这种环境的透视我们可以在对CubeMap采样时,使用视线关于物体法线的反射向量作为方向向量,就得到了环境透射的效果。

texCube(_CubeMap, refract(viewDir,normal,_RefractRatio));

_RefractRatio是折射相对系数,与物体的介质有关。

斯涅尔定律:入射角的正弦值与折射角的正弦值的比值为一定值,而此一定值跟入射与折射介质有关.
这里写图片描述
sinθ比值等于介质折射率n的比值

材质 折射指数
空气 1.00
1.33
1.309
玻璃 1.52
宝石 2.42

环境透视的实现原理

这里写图片描述

Unity Shader实现Refract CubeMap(基于CubeMap的折射效果)

折射CubeMap的Shader代码

Shader "Hidden/CMRefract"
{
    Properties
    {
        _CubeMap("CubeMap", CUBE) = ""{}
        _RefractRatio("Refract Ratio", Float) = 0.5
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;               
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldRef : TEXCOORD1;
                float3 worldPos : TEXCOORD2;
                float3 worldViewDir : TEXCOORD3;
                float4 vertexLocal : TEXCOORD4;
            };

            float _RefractRatio;
            v2f vert (appdata v)
            {
                v2f o;
                o.vertexLocal = v.vertex;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                o.worldNormal = mul(unity_ObjectToWorld, v.normal);
                o.worldViewDir =  normalize(_WorldSpaceCameraPos.xyz - o.worldPos.xyz);
                o.worldRef = refract(-o.worldViewDir,normalize(o.worldNormal),_RefractRatio);
                return o;
            }

            samplerCUBE _CubeMap;
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = texCUBE(_CubeMap, normalize(i.worldRef));
                return col;
            }
            ENDCG
        }
    }
}

折射效果展示

这里写图片描述

四、菲涅尔反射(同时考虑折射与反射)

菲尼尔现象是指光到达材质交界处时,一部分被反射,一部分被折射,即视线垂直于表面时,夹角越小,反射越明显。所有物体都有菲尼尔反射,只是强大大小不同。因此,菲尼尔反射是为了模拟真实世界中的这种光学现象。多少光发生反射,多少光发生折射,可以用菲涅尔公式进行计算。

实际世界的菲涅尔公式非常复杂,我们同样用一些近似公式来计算,如下面提到的Schlick菲涅尔近似公式和Empricial菲涅尔近似公式:

  • Schlick菲涅尔近似等式
//resnelBias 菲尼尔偏移系数  
//fresnelScale 菲尼尔缩放系数  
//fresnelPower 菲尼尔指数  
reflectFact = fresnelBias + fresnelScale*pow(1-dot(viewDir,N)),fresnelPower);  //系数:多少光发生折射多少光发生反射
  • Empricial菲涅尔近似等式:
//F0是反射系数用于控制菲涅尔的强度
reflectFact = F0 + (1-F0)*pow(1-dot(viewDir,N),5);

这里写图片描述

有了反射系数,我们就可以根据能量守恒计算反射光线和折射光线的强度了。

c(reflect) = reflect(viewDir,N)
c(refract) = reflect(viewDir,N,eatRadio)
C(fresnelFinal) = reflectFact * C(reflect) + (1-reflectFact)*C(refract);  //能量守恒

以Schlick菲涅尔公式为例实现Fresnel反射

CubeMap的菲涅尔反射Shader代码

Shader "Hidden/Fresnel_Schlick"
{
    Properties
    {
        _CubeMap("CubeMap", CUBE) = ""{}
        _RefractRatio("Refract Ratio", Float) = 0.5
        _FresnelScale("Fresnel Scale", Float) = 0.5
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;               
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldReflect : TEXCOORD1;
                float3 worldPos : TEXCOORD2;
                float3 worldViewDir : TEXCOORD3;
                float4 vertexLocal : TEXCOORD4;
                float3 worldRefract : TEXCOORD5;
            };

            float _RefractRatio;
            v2f vert (appdata v)
            {
                v2f o;
                o.vertexLocal = v.vertex;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                o.worldNormal = mul(unity_ObjectToWorld, v.normal);
                o.worldViewDir =  normalize(_WorldSpaceCameraPos.xyz - o.worldPos.xyz);
                o.worldReflect = reflect(-o.worldViewDir,normalize(o.worldNormal));
                o.worldRefract = refract(-o.worldViewDir,normalize(o.worldNormal),_RefractRatio);
                return o;
            }

            float _FresnelScale;
            samplerCUBE _CubeMap;
            fixed4 frag (v2f i) : SV_Target
            {
                float4 fresnelReflectFactor = _FresnelScale + (1 - _FresnelScale)*pow(1-dot(i.worldViewDir,i.worldNormal), 5);
                fixed4 colReflect = texCUBE(_CubeMap, normalize(i.worldReflect));
                fixed4 colRefract = texCUBE(_CubeMap, normalize(i.worldRefract));
                fixed4 col = fresnelReflectFactor * colReflect + (1-fresnelReflectFactor) * colRefract;
                return col;
            }
            ENDCG
        }
    }
}

菲涅尔反射效果展示

这里写图片描述

参考

UnityShader入门精要学习笔记(十四):立方体纹理
openGL CG 系列教程5 – Environment Mapping ( 环境贴图 )
立方体贴图
学习OpenGL ES之基于CubeMap的反射效果

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/v_xchen_v/article/details/79474193

智能推荐

linux设置定时任务脚本_一直在路上ha~的博客-程序员宝宝

记录一下在CentOS上设置定时脚本的方式:参考:https://help.aliyun.com/knowledge_detail/41445.html?spm=a2c4e.11153987.0.0.70476e57sJGuwL

使用Nuxt重构已有Vue项目(Axios默认根域名、切换Vuetify字体、配置Mavon编辑器和常见异常解决localStorage/window/document is not defined)_Piconjo_Official的博客-程序员宝宝

近日用Vue写了个小项目 但由于Vue的特性 导致项目的SEO并不是很好因此用Nuxt重构了该项目 对项目进行了一次优化由于之前并没有相关的经验 因此遇到不少的坑 特此记录一下一、概念什么是NuxtNuxt.js是Vue.js的通用框架 常用来作服务器端渲染(Server Side Rendering 简称SSR)Vue适合用于开发SPA单页面应用 但因其数据绑定特性 导致爬虫只能爬取页面模板 并不能爬到渲染好的数据 不利于网站排名对于公司内部网站等不需要排名的网站 可以不用SEO但对于那些

iis 搭建ftp服务器_weixin_38095675的博客-程序员宝宝

1、通过控制面板打开 windows 功能2、搜索iis,进入iis服务管理器3、选择网站,右键添加 ftp 站点---填写ftp服务器名、物理路径4、填写ftp服务器ip地址(选择本机地址)、ssl选择无5、填写身份验证,注意此处选择指定用户,填写的用户名需要与cmd命令窗口用户一致,不然登录时候总是填写不对用户名和密码,点击完成6、测试是否成功ftp://192...

黄聪:WIN7系统Virtualbox虚拟机VRDP server port 3389 is already in use 解决办法_weixin_33979745的博客-程序员宝宝

这两天刚刚接触虚拟机VirtualBox但是启动虚拟机发现一个错误:VRDP server port 3389 is already in use解决方法:其原因是VirtualBOX用的3389端口已经被windows的远程桌面占用,只要把windows的远程桌面(我的电脑-属性-允许用户远程链接到此计算机)暂时禁用就可以正常启动了。转载于:https://ww...

【百度地图API】如何进行地址解析与反地址解析?——模糊地址能搜索到精确地理信息!_君望永远的博客-程序员宝宝

摘要:  什么是地址解析?  什么是反地址解析?  如何运用地址解析,和反地址解析?  可以同时运用地址解析,和反地址解析麼?答案是,可以的。详见最后一个示例与代码。---------------------------------------------------------------------------------一、地址解析地址

Charles 中文乱码_林夕5464的博客-程序员宝宝_charles返回结果乱码

使用Charles抓包,抓取到request和response中中文乱码,解决步骤如下:点击Proxy -- > SSL Proxying Aetting -- > 在SSL Proxying 标签下勾选Enable SSL P roxying ,然后Add 添加Location;具体如下图:上面设置完之后,然后Chrome浏览器再访问链接的时候,返现会报错:“您的链接...

随便推点

Qt项目实战之文本编辑器---------第七集_PureヾChan的博客-程序员宝宝

上一集中我们实现了打开文档操作,那么紧接着就是去实现保存文档操作!我们想,打开文件,保存文件,是真的属于主窗口类么?其实并不是,文件的操作是属于子类的,进而保存文档的方法也应该是属于ChildWnd的成员方法。主窗口实际上也是间接调用。在ChildWnd.h中添加函数:public: bool saveDoc();//保存文档 bool saveAsDoc();//另存为文档 bool saveDocOpt(QString &docName);//真正执行保存操作

将用户密码变为md5值保存在数据库_想的昵称都存在了的博客-程序员宝宝

加密一般有两种,即双向加密和单项加密,双向加密最常用,他既能加密又能解密,单项加密只能对数据进行加密,不能解密,MD5就是单项加密,MD5加密是根据指定的密码和哈希算法生成一个适合于存储在配置文件中的哈希码!MD5是哈希/摘要算法例1:软件名称+用户名称+用户电子邮件地址+软件公司名称+用户密码+前面所有内容之和的反转文字得到的字符串,并校验其长度不大于300个字符,并且不小于50个字符。

java正则表达式分割字符串_Java开发笔记(三十七)利用正则串分割字符串_蛋丁蛋丝的博客-程序员宝宝

前面介绍了处理字符串的常用方法,还有一种分割字符串的场景也很常见,也就是按照某个规则将字符串切割为若干子串。分割规则通常是指定某个分隔符,根据字符串内部的分隔符将字符串进行分割,例如逗号、空格等等都可以作为字符串的分隔符。正好String类型提供了split方法用于切割字符串,只要字符串变量调用split方法,并把分隔符作为输入参数,该方法即可返回分割好的字符串数组。下面的split调用代码例子演...

栈和队列的应用相关习题及详解 ——数据结构_远在远方的风比远方更远的博客-程序员宝宝

习题部分选择题第一题一个问题的递归算法求解和其相对应的非递归算法求解() A. 递归算法通常高效一些 B. 非递归算法通常高效一些 C. 两者相同 D. 无法比较第二题执行()操作时,需要使用队列作为辅助存储空间 A. 查找散列(哈希)表 B. 广度优先搜索图 C. 前序(根)遍历二叉树 D. 深度优先搜索图第三题已知操作符包括‘+’‘-’‘’‘/’‘(’‘)’ 。将

mysql中的rman备份_RMAN备份详解(转载)_鵬鵬鵬鵬的博客-程序员宝宝

网上查到一篇RMAN工具的使用,这篇文档记录写的比较详细,自己目前的工作环境反复使用的几率不高,有些时候就容易忘,特此记录下来。--======================--======================一、数据库备份与RMAN备份的概念1.数据库完全备份:按归档模式分为归档和非归档归档模式打开状态,属于非一致性备份关闭状态,可以分为一致性和非一致性非归档模式打开状态,非一致性...

马士兵高并发编程笔记二之容器读写同步_Rogera7的博客-程序员宝宝

容器读写同步,实时通知实现一个容器,提供两个方法,add,size, 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束1. volatile方法public class MyContainer2 { //添加volatile,使t2能够得到通知 volatile List lists = new Arra...

推荐文章

热门文章

相关标签