Chapter10_高级纹理

1.立方体纹理

用于实现环境映射。模拟物体映射周围的环境。立方体纹理包含六张贴图(前后左右上下)。

局限:场景发生变化(物体,光源增减,物体位置变化),立方体纹理就得重新生产。

1.1 立方体纹理创建方式

  1. 直接使用带特殊布局的纹理。然后设置TextureTpye = Cubemap即可。Unity官方推荐使用
  2. 创建一个Cubemap,然后提供前后左右上下六张贴图。
  3. 调用Camera.RenderToCubemap将当前摄像机观察到的图像渲染存储到Cubemap中。

1.2 利用立方体纹理实现物体反射场景环境效果

原理:

 

图中向量I表示观察方向(摄像机到顶点的方向),N表示顶点对应的法向量,R表示反射向量。

用反射向量R对立方体纹理进行采样(反射向量和立方体纹理其中的一张贴图相交,采样相交点的纹理。),然后叠加到原来的纹理上,形成反射周边环境的效果。

反射向量计算如下图:

Shader "Chan/Chapter10_Reflection" {
    Properties
    {
        _Color("Color Tint",Color) = (1,1,1,1)
        _ReflectColor("Reflect Color",Color) = (1,1,1,1)
        _ReflectAmout("Reflect Amout",Range(0,1)) = 0.5
        _Cubemap("Reflection Cubemap",Cube) = "_Skybox"{}
    }
    SubShader
    {
        Tags{"RenderType" = "Opaque" "Queue" = "Geometry"}
        
        Pass
        {
            Tags{"LightMode" = "ForwardBase"}

            CGPROGRAM
            
            #pragma multi_compile_fwdbase

            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            fixed4 _Color;
            fixed4 _ReflectColor;
            float _ReflectAmout;
            samplerCUBE _Cubemap;

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

            struct v2f
            {
                float4 pos:SV_POSITION;
                float3 worldNormal:Texcoord0;
                fixed3 worldPos:Texcoord1;
                fixed3 worldViewDir:Texcoord2;
                fixed3 worldRefl:Texcoord3;
                SHADOW_COORDS(4)
            };

            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                //世界坐标系下求得顶点的反射方向,然后拿着反射方向去跟立方体纹理进行采样
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
                o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
                o.worldRefl = reflect(-o.worldViewDir,o.worldNormal);
                TRANSFER_SHADOW(o);
                return o;
            }

            fixed4 frag(v2f i):SV_Target
            {
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                fixed3 worldViewDir = normalize(i.worldViewDir);

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0,dot(worldNormal,worldLightDir));

                //用反射方向向量对立方体纹理进行采样
                fixed3 reflection = texCUBE(_Cubemap,i.worldRefl).rgb * _ReflectColor.rgb;

                UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);

                //设置一个参数_ReflectAmout,对漫反射颜色和采样立方体纹理得到的反射颜色,进行混合
                fixed3 color = ambient + lerp(diffuse,reflection,_ReflectAmout) * atten;

                return fixed4(color,1);
            }

            ENDCG
        }
    }
    Fallback "Reflective/VertexLit"
}

实现思路:

  1. 世界坐标系下求得顶点的反射方向(CG函数 reflect),然后拿着反射方向去跟立方体纹理进行采样。
  2. 用反射方向向量对立方体纹理进行采样,使用CG方法texCUBE。
  3. 设置一个参数_ReflectAmout,对漫反射颜色和采样立方体纹理得到的反射颜色,进行混合。
     

1.3 利用立方体纹理实现物体折射场景环境效果

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Chan/Chapter10_Refraction" {
    Properties
    {
        _Color("Color Tint",Color) = (1,1,1,1)
        _RefractColor("Refract Color",Color) = (1,1,1,1)
        _RefractAmout("Refract Amout",Range(0,1)) = 0.5
        _RefractRation("Refract Ration",Range(0,1)) = 0.5
        _Cubemap("Refraction Cubemap",Cube) = "_Skybox"{}
    }
    SubShader
    {
        Tags{"RenderType" = "Opaque" "Queue" = "Geometry"}
        pass
        {
            Tags{"LightMode" = "ForwardBase"}
            
            CGPROGRAM
            
            #include "Lighting.cginc"
            #include "AutoLight.cginc"
            #pragma vertex vert
            #pragma fragment frag

            float4 _Color;
            fixed3 _RefractColor;
            float _RefractAmout;
            float _RefractRation;
            samplerCUBE _Cubemap;

            struct a2v
            {
                float4 vert:POSITION;
                float3 normal:NORMAL;
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
                float3 worldNormal:Texcoord0;
                fixed3 worldPos:Texcoord1;
                fixed3 worldViewDir:Texcoord2;
                fixed3 worldRefl:Texcoord3;
                SHADOW_COORDS(4)
            };

            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vert);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld,v.vert).xyz;
                o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);

                //CG refract函数计算折射方向,传入参数 1.入射光线方向,2.表面法线向量,3.入射光线所在介质和折射光线所在介质的折射率之比 
                o.worldRefl = refract(-normalize(o.worldViewDir),normalize(o.worldNormal),_RefractRation);

                TRANSFER_SHADOW(o); 
                return o;
            }

            fixed4 frag(v2f i):SV_Target
            {
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                fixed3 worldViewDir = normalize(i.worldViewDir);

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0,dot(worldNormal,worldLightDir));

                fixed3 refraction = texCUBE(_Cubemap,i.worldRefl).rgb * _RefractColor.rgb;

                UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);

                fixed3 color = ambient + lerp(diffuse,refraction,_RefractAmout) * atten;

                return fixed4(color,1);
            }

            ENDCG
        }
    }

    Fallback ""

}

实现思路:

  1. 世界坐标系下求得顶点的折射方向(CG函数 refract),然后拿着折射方向去跟立方体纹理进行采样。
  2. 用折射方向向量对立方体纹理进行采样,使用CG方法texCUBE。
  3. 设置一个参数_ReflectAmout,对漫反射颜色和采样立方体纹理得到的反射颜色,进行混合。

菲涅尔反射

当视线与物体表面夹角越小(越平行于表面),反射越明显。入射光线和反射光线之间存在一定的比率关系。

Schlick 菲涅尔近似等式:
                                       \large F_{schlick}(v,n) = F_{0} + (1-F_{0}))(1-v.n)^{5}

    其中\large F_{0}是反射系数,用于控制菲涅尔反射强度。v是视角方向,n是表面法线。

Empricial 菲涅尔近似等式:

                               \large F_{Empricial}(v,n) = max(0,min(1,bias + scale) * (1-v.n)^{power}))

bias,scale,poser都是控制参数

Shader "Chan/Chapter10_Fresnel" {
    Properties
    {
        _Color("Color Tint",Color) = (1,1,1,1)
        //反射系数
        _FresnelScale("Fresnel Scale",Range(0,1)) = 0.5
        _Cubemap("Reflection Cubemap",Cube) = "_Skybox"{}
    }
    SubShader
    {

        Tags{"RenderType" = "Opaque" "Queue" = "Geometry"}
        Pass
        {
            Tags{"LightMode" = "ForwardBase"}
            CGPROGRAM
            
            #pragma multi_compile_fwdbase
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            fixed4 _Color;
            float _FresnelScale;
            samplerCUBE _Cubemap;

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

            struct v2f
            {
                float4 pos:SV_POSITION;
                float3 worldNormal:Texcoord0;
                fixed3 worldViewDir:Texcoord1;
                fixed3 worldPos:Texcoord2;
                fixed3 worldRefl:Texcoord3;
                SHADOW_COORDS(4)
            };

            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                //世界坐标系下求得顶点的反射方向,然后拿着反射方向去跟立方体纹理进行采样
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
                o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
                o.worldRefl = reflect(-o.worldViewDir,o.worldNormal);
                TRANSFER_SHADOW(o);
                return o;
            }

            fixed4 frag(v2f i):SV_Target
            {
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                fixed3 worldViewDir = normalize(i.worldViewDir);

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);

                //利用texCUBE函数,传入参数反射方向向量对立方体纹理进行采样
                fixed3 reflection = texCUBE(_Cubemap,i.worldRefl).rgb;

                //Schlick 菲涅尔近似等式计算入射光线和反射光线比率大小的值 fresnel
                fixed fresnel = _FresnelScale + (1-_FresnelScale) * pow(1-dot(worldViewDir,worldNormal),5);

                fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0,dot(worldNormal,worldLightDir));

                fixed3 color = ambient + lerp(diffuse,reflection,saturate(fresnel)) * atten;
                return fixed4(color,1.0);
            }

            ENDCG
        }
    }
    Fallback "Reflective/VertexLit"
}


Schlick 菲涅尔近似等式计算入射光线和反射光线比率大小的值 fresnel。

然后用Schlick对漫反射光照和反射光照用fresnel进行插值。


 2.渲染纹理

渲染目标纹理:现代GPU允许我们,把整个场景渲染到一个中间缓冲区渲染目标纹理(Render Target)中,而不是传统的的帧缓冲或者后备缓冲区(back buffer),然后显示到屏幕上。Unity中有专门的渲染目标纹理类型 - 渲染纹理(Render Texture)

多重渲染目标纹理:利用这种技术,可以实现多重渲染目标(Multiple Render Target,MRT)。这种技术允许我们把场景同事渲染到多个渲染目标纹理中,不再需要每个渲染目标单独渲染整个场景。

2.1 使用渲染纹理(Render Texture)

  1. 在Unity中直接通过菜单创建(Create-Render Texture),然后把某个摄像机的渲染目标设置成该渲染纹理。
  2. 在屏幕后处理时使用GrabPass命令或者OnRenderImage函数来获取当前屏幕图像。Unity会把当前屏幕图像存放到一张和屏幕分辨率等同的渲染纹理中。在后续的自定义Pass中就可当做普通的纹理来处理,实现各种屏幕特效。

镜子效果:

Shader "Chan/Chapter10_Mirror" {
    Properties
    {
        _MainTex("Main Tex",2D) = "white"{}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Geometry"}
        Pass
        {
            CGPROGRAM       
            #pragma vertex vert
            #pragma fragment frag

            sampler2D _MainTex;

            struct a2v
            {
                float4 vertex:POSITION;
                float3 texcoord:TEXCOORD0;              
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
                float2 uv:Texcoord0;
            };

            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.texcoord;
                //镜面,左右相反 因此翻转uv的x分量
                o.uv.x = 1 - o.uv.x;
                return o;
            }

            fixed4 frag(v2f i):SV_Target
            {
                return tex2D(_MainTex,i.uv);
            }
            ENDCG
        }
    }
    Fallback Off
}

实现步骤:

  1. 创建一个摄像机,调整摄像机参数,用于照射镜面效果所需要的画面。
  2. 创建一个渲染纹理(Render Texture),设置摄像机的Target Texture为渲染纹理。把摄像机所照到的图面渲染存储到渲染纹理中。
  3. 创建一个四边形Quad,调整大小坐标角度参数。作为镜子使用。设置渲染纹理为四边形材质的Main Texture。

玻璃效果:

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Chan/Chapter10_GlassRefraction" {
    Properties
    {
        _MainTex("Main Tex",2D) = "white"{}
        _BumpMap("Normal Map",2D) = "white"{}
        _CubeMap("Cube Map",Cube) = "_Skybox"{}
        //控制折射时候图像的扭曲程度
        _Distortion("Distortion",Range(0,100)) = 10
        //控制折射程度
        _RefractAmount("Refract Amount",Range(0.0,1.0)) = 1.0
    }
    SubShader
    {
        //GrabPass 通常用于渲染透明物体,因此渲染队列设置为Transparent
        Tags{"Queue" = "Transparent" "RenderType" = "Opaque"}

        //GrabPass 抓取屏幕图像,存到一张纹理,给这个纹理取个名字叫 _RefractionTex
        GrabPass{"_RefractionTex"}

        Pass
        {
            Tags{"LightMode" = "Forwardbase"}

            CGPROGRAM
// Upgrade NOTE: excluded shader from DX11; has structs without semantics (struct v2f members TtoW0,TtoW0,TtoW0)
#pragma exclude_renderers d3d11
            
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _BumpMap;
            float4 _BumpMap_ST;
            samplerCUBE _CubeMap;
            float _Distortion;
            fixed _RefractAmount;
            sampler2D _RefractionTex;
            //_RefractionTex 纹理的纹素 对屏幕图像进行采样坐标偏移时使用的变量
            float4 _RefractionTex_TexelSize;

            struct a2v
            {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
                float4 tangent:TANGENT;
                float2 texcoord:TEXCOORD0;
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
                float4 scrPos:TEXCOORD0;
                float4 uv:TEXCOORD1;
                float4 TtoW0:TEXCOORD2;
                float4 TtoW1:TEXCOORD3;
                float4 TtoW2:TEXCOORD4;
            };

            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);

                //得到抓取的屏幕图像_RefractionTex 的采样坐标 
                o.scrPos = 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).xyz;
                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 = normalize(UnityWorldSpaceViewDir(worldPos));

                //对法线纹理进行采样,得到切线空间下的法线方向 (默认就是切线空间下)
                fixed3 bump = UnpackNormal(tex2D(_BumpMap,i.uv.zw));

                //用切线空间下的法线方向,对屏幕图像的采样坐标进行偏移,模拟折射效果
                float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
                i.scrPos.xy = offset + i.scrPos.xy;

                //i.scrPos.xy/i.scrPos.w = 透视除法得到真正的屏幕坐标
                //用真正的屏幕坐标对抓取的屏幕图像进行采样
                fixed3 refrCol = tex2D(_RefractionTex,i.scrPos.xy/i.scrPos.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 * (1 - _RefractAmount) + refrCol * _RefractAmount;

                return fixed4(finalColor,1);
            }

            ENDCG
        }
    }

}

实现思路:

  1. Tags{"Queue" = "Transparent" "RenderType" = "Opaque"}      GrabPass{"_RefractionTex"}   在所有不透明物体渲染完成后,抓取一张屏幕图像
  2. 把法线方向从切线空间转换到世界空间下,然后得到视角方向相对于法线方向的反射方向
  3. 用反射方向对立方体纹理采样。
  4. 根据参数混合,得到最终的输出。

渲染纹理(Render Texture) + 摄像机 和 GrabPass 都可以抓取屏幕图像

  • 渲染纹理(Render Texture)  效率高,可以自定义抓取的纹理大小,摄像机可选择渲染对象
  • GrabPass 实现简单,效率低。抓取纹理大小就是屏幕尺寸。

程序纹理

程序纹理-计算机生成的图像。由于是程序生成,因此可以由程序控制,实现各种动画,视觉效果。

Unity的程序材质

专门使用程序纹理的材质,叫做程序材质。用Substance Designer是非常出色的纹理生成工具。它会生成.sbsar后缀的文件,当把文件导入Unity后,Unity就会自动生成包含一个或多个程序材质的的程序纹理资源。

程序材质使用和普通材质使用一样。程序材质通过调整程序纹理的多个属性来控制纹理外观。

参考:

https://blog.csdn.net/v_xchen_v/article/details/79474193

https://blog.csdn.net/wangdingqiaoit/article/details/52557544

https://blog.csdn.net/v_xchen_v/article/details/79692726?utm_source=blogxgwz9

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值