Unity Shader学习(三)---纹理

本文详细介绍了纹理映射技术的基本概念及其多种应用形式,包括基本的纹理映射、凹凸映射、渐变纹理、遮罩纹理、立方体纹理及渲染纹理等。深入探讨了这些技术的具体实现方法和在游戏开发中的应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

纹理映射技术就是把一张图粘贴在模形的表面上

(一)用一张纹理来代替物体的漫反射颜色

 // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 6/Blinn-Phong" {
    Properties {
        _Color ("Color Tint ",Color) = (1,1,1,1)
        _MainTex ("Main Tex",2D) = "white"{}//2D 是纹理属性
        _Specular ("Specular"Color) = (1111)
        _Gloss ("Gloss"Range(8.0256)) = 20
    }
    SubShader {
        Pass { 
            Tags { "LightMode"="ForwardBase" }
        
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            
            #include "Lighting.cginc"
            
            fixed4 Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;//纹理名_ST方式声明某个纹理的属性 _MainTex_ST.xy 存储的是缩放值,_MainTex_ST.zw存储的是偏移值

            fixed4 _Specular;
            float _Gloss;
            
            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;//将第一组纹理坐标存储在这个变量中
            };
            
            struct v2f {
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
                float2 uv : TEXCOORD2;// 存储纹理的坐标
            };
            
            v2f vert(a2v v) {
                v2f o;
                // Transform the vertex from object space to projection space
                o.pos = UnityObjectToClipPos(v.vertex);
                
                // Transform the normal from object space to world space
                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
                
                // Transform the vertex from object spacet to world space
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
                //或者用o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);进行纹理坐标变换
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target {
                // Get ambient term
                fixed3 albedo = tex2D(_MainTex,i.uv).rgb * _Color.rgb;
                //tex2D 用来对纹理进行采样,第一个参数_MainTex为纹理,i.uv为该点的纹素值,
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                
                // Compute diffuse term
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0dot(worldNormal, worldLightDir));
                
                // Get the view direction in world space
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                // Get the half direction in world space
                fixed3 halfDir = normalize(worldLightDir + viewDir);
                // Compute specular term
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0dot(worldNormal, halfDir)), _Gloss);
                
                return fixed4(ambient + diffuse + specular1.0);
            }
            
            ENDCG
        }
    } 
    FallBack "Specular"
}
纹理的类型:Texture, Normal map, Cubemap等

Alpha from Grayscale:设置透明通道的

Wrap Mode:可以设置为Repeat ,也可以设置为Tiling平铺

Filter Mode :滤波,Point Bilinear Trilinear三种,滤波效果一次提升,耗费也会变大

多级渐远纹理:提前用滤波技术得到很多小的图像,形成一个金字塔形,每一层都是对上一层采样的结果。当摄像机距离较远时,会使用小的纹理。这种技术会占用很多内存,以空间换取时间。

Advance类型的纹理,可以设置 Generate Mip Maps多级渐远纹理

导入的纹理可以是非正方形的,但是长宽的大小应该是2的幂,例如2,4,8等。如果非2的幂,会占用更多的内存空间,GPU读取纹理的速度也会下降。

Format决定了压缩的格式

(二)凹凸映射

凹凸映射目的是用一张纹理来修改模型表面的法线,为模型提供更多的细节。基本思想是通过改变表面法线,而不是使用纹理来改变光照方程中的颜色分量

实现凹凸映射有两种方法:

高度映射:利用高度纹理来模拟表面位移,得到一个修改后的法线值。

法线映射:使用一张法线纹理来直接存储表面法线,这种方法为法线映射。

1.高度纹理:用一张高度图来实现,主要是颜色不同,浅色表明向外凸起,深色为凹陷

2.法线纹理:法线纹理存储的就是表面的法线方向,由于法线的方向分量范围在[-1,1],而像素的分量范围在[0,1],需要做一个映射。

 pixel = (normal+1)/2 normal = pixel * 2 -1

模型空间中的表面法线存储在一张纹理中,为模型空间法线纹理。实际应用中常用模型顶点的切线空间来存储法线,称为切线空间的法线纹理。

(1)切线空间下光照模型

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 7/Normal Map In Tangent Space" {
    Properties {
        _Color ("Color Tint"Color) = (1111)
        _MainTex ("Main Tex"2D) = "white" {}
        _BumpMap ("Normal Map"2D) = "bump" {}//bump是Unity自带法线纹理,没有提供任何法线纹理时。表示对应了模型自带的法线纹理
        _BumpScale ("Bump Scale"Float) = 1.0//控制凹凸程度的
        _Specular ("Specular"Color) = (1111)
        _Gloss ("Gloss"Range(8.0256)) = 20
    }
    SubShader {
        Pass { 
            Tags { "LightMode"="ForwardBase" }
        
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            
            #include "Lighting.cginc"
            
            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _BumpMap;
            float4 _BumpMap_ST;
            float _BumpScale;
            fixed4 _Specular;
            float _Gloss;
            
            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;//利用tangent的第四个变量tangent.w来确定切线空间中的第三个坐标轴,副切线的方向
                float4 texcoord : TEXCOORD0;//第一组纹理坐标
            };
            
            struct v2f {
                float4 pos : SV_POSITION;
                float4 uv : TEXCOORD0;
                float3 lightDir: TEXCOORD1;
                float3 viewDir : TEXCOORD2;
            };

            // Unity doesn't support the 'inverse' function in native shader
            // so we write one by our own
            // Note: this function is just a demonstration, not too confident on the math or the speed
            // Reference: http://answers.unity3d.com/questions/218333/shader-inversefloat4x4-function.html
            float4x4 inverse(float4x4 input) {
                #define minor(a,b,c) determinant(float3x3(input.a, input.b, input.c))
                
                float4x4 cofactors = float4x4(
                     minor(_22_23_24, _32_33_34, _42_43_44), 
                    -minor(_21_23_24, _31_33_34, _41_43_44),
                     minor(_21_22_24, _31_32_34, _41_42_44),
                    -minor(_21_22_23, _31_32_33, _41_42_43),
                    
                    -minor(_12_13_14, _32_33_34, _42_43_44),
                     minor(_11_13_14, _31_33_34, _41_43_44),
                    -minor(_11_12_14, _31_32_34, _41_42_44),
                     minor(_11_12_13, _31_32_33, _41_42_43),
                    
                     minor(_12_13_14, _22_23_24, _42_43_44),
                    -minor(_11_13_14, _21_23_24, _41_43_44),
                     minor(_11_12_14, _21_22_24, _41_42_44),
                    -minor(_11_12_13, _21_22_23, _41_42_43),
                    
                    -minor(_12_13_14, _22_23_24, _32_33_34),
                     minor(_11_13_14, _21_23_24, _31_33_34),
                    -minor(_11_12_14, _21_22_24, _31_32_34),
                     minor(_11_12_13, _21_22_23, _31_32_33)
                );
                #undef minor
                return transpose(cofactors) / determinant(input);
            }

            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                //进行纹理坐标变换
                o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
                o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

                ///
                /// Note that the code below can handle both uniform and non-uniform scales
                ///

                // Construct a matrix that transforms a point/vector from tangent space to world space
                fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);  
                fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);//切线转换到世界空间  
                fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; //因为与切线和法线都垂直的方向有两个,因此需要确定哪个方向

                /*
                float4x4 tangentToWorld = float4x4(worldTangent.x, worldBinormal.x, worldNormal.x, 0.0,
                                                   worldTangent.y, worldBinormal.y, worldNormal.y, 0.0,
                                                   worldTangent.z, worldBinormal.z, worldNormal.z, 0.0,
                                                   0.00.00.01.0);
                // The matrix that transforms from world space to tangent space is inverse of tangentToWorld
                float3x3 worldToTangent = inverse(tangentToWorld);
                */
                
                //wToT = the inverse of tToW = the transpose of tToW as long as tToW is an orthogonal matrix.
                float3x3 worldToTangent = float3x3(worldTangent, worldBinormal, worldNormal);
                //TARGENT_SPACE_ROTATION 可以直接得到ration的变换矩阵

                // Transform the light and view dir from world space to tangent space
                o.lightDir = mul(worldToTangent, WorldSpaceLightDir(v.vertex));
                o.viewDir = mul(worldToTangent, WorldSpaceViewDir(v.vertex));

                ///
                /// Note that the code below can only handle uniform scales, not including non-uniform scales
                /// 

                // Compute the binormal
//                float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w;
//                // Construct a matrix which transform vectors from object space to tangent space
//                float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);
                // Or just use the built-in macro
//                TANGENT_SPACE_ROTATION;
//                
//                // Transform the light direction from object space to tangent space
//                o.lightDir = mul(rotation, normalize(ObjSpaceLightDir(v.vertex))).xyz;
//                // Transform the view direction from object space to tangent space
//                o.viewDir = mul(rotation, normalize(ObjSpaceViewDir(v.vertex))).xyz;
                
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target {                
                fixed3 tangentLightDir = normalize(i.lightDir);
                fixed3 tangentViewDir = normalize(i.viewDir);
                
                // Get the texel in the normal map
                fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);//纹理采样
                fixed3 tangentNormal;
                // If the texture is not marked as "Normal map"如果纹理没有设置为法线贴图,需要映射回法线方向
//                tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;
//                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
                
                // Or mark the texture as "Normal map", and use the built-in funciton
                tangentNormal = UnpackNormal(packedNormal);//计算得到正确的法线方向
                tangentNormal.xy *= _BumpScale;
                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
                
                fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
                
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                
                fixed3 diffuse = _LightColor0.rgb * albedo * max(0dot(tangentNormal, tangentLightDir));

                fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0dot(tangentNormal, halfDir)), _Gloss);
                
                return fixed4(ambient + diffuse + specular1.0);
            }
            
            ENDCG
        }
    } 
    FallBack "Specular"
}
当把法线纹理的纹理标识成Normal Map,使用Unity内置的函数 UnpackNormal来得到正确的法线方向。

Create from Grayscale用于从高度图中生成法线纹理。高度图本身记录的是相对高度,是一张灰度图,白色表示相对更高,黑色表示相对更低。

(三)渐变纹理

渐变纹理是利用

Shader "Unity Shaders Book/Chapter 6/Blinn-Phong" {
    Properties {
        _Color ("Color Tint ",Color) = (1,1,1,1)
        _RampTex ("Ramp Tex",2D) = "white"{}
        _Specular ("Specular"Color) = (1111)
        _Gloss ("Gloss"Range(8.0256)) = 20
    }
    SubShader {
        Pass { 
            Tags { "LightMode"="ForwardBase" }
        
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            
            #include "Lighting.cginc"
            
            fixed4 Color;
            sampler2D _RampTex;
            float4 _RampTex_ST;//纹理名_ST方式声明某个纹理的属性 _MainTex_ST.xy 存储的是缩放值,_MainTex_ST.zw存储的是偏移值
            fixed4 _Specular;
            float _Gloss;
            
            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;//将第一组纹理坐标存储在这个变量中
            };
            
            struct v2f {
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
                float2 uv : TEXCOORD2;
            };
            
            v2f vert(a2v v) {
                v2f o;
                o.pos = mul(UINTY_MATRIX_MVP,v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(_ObjectToWorld,v.vertex).xyz;
                o.uv = TRANSFROM_TEX(v.texcoord,_RampTex);//计算平铺和偏移后的纹理坐标
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target {
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed halfLambert = 0.5 * dot(worldNormal , worldLightDir) + 0.5;
                fixed3 diffuseColor = tex2D(_RampTex , fixed2(halfLambert,halfLambert)).rgb * _Color.rgb;//利用半兰伯特模型实现漫反射光照,利用halfLambert纹理坐标对渐变纹理_RampTex进行采样,_RampTex实际就是一个一维纹理,纵轴方向上颜色不变,因此纹理坐标u v方向都适用halfLambert,然后从渐变纹理采样得到的颜色和材质颜色相乘,得到漫反射的颜色

                fixed3 diffuse = _LightColor0.rgb * diffuseColor;
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                fixed3 halfDir = normalize(worldLightDir+viewDir);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)),_Gloss);
                return (ambient + diffuse + specular,1.0);
            }
            ENDCG
        }
    } 
    FallBack "Specular"
}

把渐变纹理的Wrap Mode的Clamp模式可以防止对纹理进行采样是由于浮点数精度而造成的问题。

(四)遮罩纹理

在创建世界地图时,做地形的材质需要混合多张图片,如草地,石子,土地等,用遮罩纹理可以控制如何混合这些纹理

遮罩纹理一般通过采样得到遮罩纹理,然后使用其中某个通道的值来与某种表面属性进行相乘,当该通道值为0时可以保护表面不受该属性影响。

Shader "Unity Shaders Book/Chapter 6/Blinn-Phong" {
    Properties {
        _Color ("Color Tint ",Color) = (1,1,1,1)
        _MainTex("Main Tex",2D) ="white"{}
        _BumpMap("Normal Map",2D) = "bump"{}//决定凹凸性的纹理,决定的是漫反射
        _BumpScale("Bump Scale",Float) = 1.0
        _SpecularMask("Specualr Mask",2D) = "white"//决定遮罩的纹理,决定的是高光反射
        _SpecularScale("Specular Scale",float) = 1.0
        _Specular ("Specular"Color) = (1111)
        _Gloss ("Gloss"Range(8.0256)) = 20
    }
    SubShader {
        Pass { 
            Tags { "LightMode"="ForwardBase" }
        
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            
            #include "Lighting.cginc"

            fixed4 Color;
            sampler2D _MainTex;
            float _MainTex_ST;
            sampler2D _BumpMap;
            float _BumpScale;
            sampler2D _SpecularMask;
            float _SpecularScale;
            fixed4 _Specular;
            float _Gloss;
            
            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
                float4 texcoord : TEXCOORD0;//将第一组纹理坐标存储在这个变量中
            };
            
            struct v2f {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 lightDir : TEXCOORD1;
                float viewDir : TEXCOORD2;
            };
            
            v2f vert(a2v v) {
                v2f o;
                o.pos = mul(UINTY_MATRIX_MVP,v.vertex);
                o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
                TANFENT_SPACE_ROTATION;
                o.lightDir = mul(rotation,ObjSpaceLightDir(v.vertex)).xyz;
                o.viewDir = mul(rotation,ObjSpaceViewDir(v.vertex)).xyz;

                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target {
                fixed3 tangentLightDir = normalize(i.lightDir);
                fixed3 tangentViewDir = normalize(i.viewDir);
                fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap,i.uv));
                tangentNormal.xy * = _BumpScale;
                tangentNormal.z = sqrt(1.0-saturate(dot(tangentNormal.xy,tangentNormal.xy)));
                fixed3 albedo = tex2D(_MainTex,i.uv).rgb * _Color.rgb;
                fixed3 ambient = UNITY_LIGHRMODEL_AMBIENT.xyz * albedo;
                fixed3 diffuse = _LightColor0.rgb * albedo * max(0,dot(tangentNormal,tangetLightDir));
                fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
                fixed3 specularMask = tex2D(_SpecularMask,i.uv).r * _SpecularScale;//对SpecularMask进行采样,这里利用r来计算掩码值
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(tangetNormal,halfDir)),_Gloss) * speculatMask;
                return fixed4(ambient + diffuse + specular,1.0);
            }
            ENDCG
        }
    } 
    FallBack "Specular"
}
 

(五)立方体纹理

(1)反射

Shader "Unity Shaders Book/Chapter 10/Reflection" {
    Properties {
        _Color ("Color Tint"Color) = (1111)
        _ReflectColor ("Reflection Color"Color) = (1111)
        _ReflectAmount ("Reflect Amount"Range(01)) = 1
        _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;
            fixed _ReflectAmount;
            samplerCUBE _Cubemap;
            
            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            
            struct v2f {
                float4 pos : SV_POSITION;
                float3 worldPos : TEXCOORD0;
                fixed3 worldNormal : TEXCOORD1;
                fixed3 worldViewDir : TEXCOORD2;
                fixed3 worldRefl : TEXCOORD3;
                SHADOW_COORDS(4)//SHADOW_COORDS这个宏后面的参数是指第几个通道,不要和其他的出现冲突,也就是要改变投影的颜色话必须要占用一个通道
            };
            
            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);
                
                // Compute the reflect dir in world space
                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(0dot(worldNormal, worldLightDir));             
                // Use the reflect dir in world space to access the cubemap
                fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb * _ReflectColor.rgb;
                //对立方体纹理采样得到反射的颜色
                UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);//得到光源的衰减              
                // Mix the diffuse color with the reflected color对漫反射和反射做一下混合插值
                fixed3 color = ambient + lerp(diffuse, reflection, _ReflectAmount) * atten;   
                return fixed4(color1.0);
            }     
            ENDCG
        }
    }
    FallBack "Reflective/VertexLit"
}

(2)折射

与上面基本相同,只是顶点着色器中计算的是

o.worldRefr = refract(-normalize(o.worldViewDir), normalize(o.worldNormal), _RefractRatio);//入射光线,表面法线,折射率来计算折射

片元着色器中计算的是:

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

UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

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

(3)菲涅耳反射

菲涅耳反射:描述了一种光学显现,当光线照射到物体表面时,一部分发生反射,一部分发生折射或散射,被反射的光和入射的光存在一定的比率关系,这个比率关系可以通过菲涅耳等式进行计算。例如,站在湖边,你会发现脚边的水是几乎透明的,而远处的水面只能看见远处。Shader基本与前面一样,顶点着色器中计算的是反射,片元着色器:

fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb;

fixed fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(worldViewDir, worldNormal), 5);

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

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

(六)渲染纹理

渲染纹理非常重要。一般来说,一个摄像机的渲染结果汇输出到颜色缓冲区中,并显示在屏幕上现代GPU允许把整个三维场景渲染到一个中间缓冲中,即渲染目标纹理(RTT),而不是传统的帧缓冲或者后备缓冲。多重渲染目标技术(MRT)指的是GPU允许我们把场景同时渲染到多个渲染目标纹理中,而不再需要为每个渲染目标纹理单独渲染完整的场景,延迟渲染就是用的多重渲染目标技术。

(1)镜子

镜子的实现:镜子的实现原理很简单,需要再创建一个摄像机,调整位置,裁剪平面视角等,使他的显示图像时我们希望的镜子图像。该摄像机不需要直接显示在屏幕上,而是用于渲染到纹理。Unity中创建一个渲染纹理 Render Texture,并把该渲染纹理在水平方向上翻转后直接显示到物体上即可。

   v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);      
                o.uv = v.texcoord;
                // Mirror needs to filp x
                o.uv.x = 1 - o.uv.x;              
                return o;
            }         
            fixed4 frag(v2f i) : SV_Target {
                return tex2D(_MainTex, i.uv);
            }

(2)玻璃

Unity中使用一种特殊的Pass来实现获取屏幕图像的目的,就是GrabPass,会把当前屏幕的图像绘制在一张纹理中,以便在后续的Pass中访问。使用GrabPass可以让我们对该物体后面的图像进行更复杂的处理,例如使用法线模拟折射效果,而不再是简单的与原屏幕颜色进行混合。GrabPass通常用于渲染透明物体,因此注意渲染队列的设置。"Queue"="Transparent"才能保证当渲染该物体时,所有的不透明的物体都已经被绘制在屏幕上。

Shader "Unity Shaders Book/Chapter 10/Glass Refraction" {
    Properties {
        _MainTex ("Main Tex"2D) = "white" {}
        _BumpMap ("Normal Map"2D) = "bump" {}
        _Cubemap ("Environment Cubemap"Cube) = "_Skybox" {}
        _Distortion ("Distortion"Range(0100)) = 10//折射时图像的扭曲程度
        _RefractAmount ("Refract Amount"Range(0.01.0)) = 1.0//反射效果
    }
    SubShader {
        // We must be transparent, so other objects are drawn before this one.
        Tags { "Queue"="Transparent" "RenderType"="Opaque" }      
        // This pass grabs the screen behind the object into a texture.
        // We can access the result in the next pass as _RefractionTex
        GrabPass { "_RefractionTex" }//定义了一个抓取屏幕图像的Pass,定义的字符串决定了会把抓取到的图像存在哪个纹理中      
        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;
            fixed _RefractAmount;
            sampler2D _RefractionTex;
            float4 _RefractionTex_TexelSize;//该纹理像素的大小        
            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT
                float2 texcoordTEXCOORD0;
            };            
            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);                
                o.scrPos = ComputeGrabScreenPos(o.pos);//得到对应被抓取的屏幕图像的采样坐标               
                o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);//计算_MainTex的采样坐标
                o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);//计算_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; 
                //由于要在片元着色器把法线从切线空间转换到世界空间,以便对CubeMap进行采样,因此需要计算从切线空间到世界空间的变换矩阵
                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));               
                // Get the normal in tangent space
               fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));//UnpackNormal对法线纹理进行采样和解码             
                // Compute the offset in tangent space 对屏幕图像进行采样坐标偏移,体现折射的效果 法线坐标*偏移*屏幕图像采样的坐标
                float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
                i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;//计算得到真正的屏幕坐标
                fixed3 refrCol = tex2D(_RefractionTex, i.scrPos.xy/i.scrPos.w).rgb;//对屏幕图像采样,得到模拟的折射颜色     
                // Convert the normal to world space 法线转换到世界空间下
                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;//利用反射方向对_Cubemap进行采样,并和主纹理方向进行相乘得到反射颜色            
                fixed3 finalColor = reflCol * (1 - _RefractAmount) + refrCol * _RefractAmount;           
                return fixed4(finalColor, 1);
            }        
            ENDCG
        }
    }
    FallBack "Diffuse"
}

GrabPass和渲染纹理+额外摄像机都可以抓取屏幕图像。GrabPass效率差一些。

GrabPass{"TextureName"}后续的Pass会利用TextureName来访问屏幕图像,每帧最多抓取一次。如果不说明图像,场景中多个物体使用的话会抓取多次,很昂贵。每帧进行多次抓取操作。


(7)深度纹理和法线纹理

深度纹理和法线纹理上进行采样等操作,会使物体不受外部光照因素影响,保存的是当前渲染物体的真实信息,类似于边缘检测等会更可靠。

深度纹理:就是一张渲染纹理,只不过它里面存储的像素值不是颜色值,而是一个高精度的深度值。这些深度值来自于顶点变换后得到的归一化的设备坐标(NDC)。变换到NDC时,z坐标是从-1 , 1之间。为了可以存储在一张图中,我们将其转换:d = 0.5 *Zndc + 0.5。d对应了深度纹理中的像素值。

在Unity中,如果使用延迟渲染,自然会把深度纹理存储在G缓冲区中。如果没有使用延迟渲染,Unity中可以设置RenderType标签为Opaque的物体,判断它们使用的渲染队列是否小于2500,如果满足就渲染到深度和法线纹理中。

Unity获取深度纹理和法线纹理

camera.depthTextureMode = DepthTextureMode.Depth

camera.depthTextureMode = DepthTextureMode.DepthNormals

 将=换为|=即可获取两种纹理

float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv) 












评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值