zoukankan      html  css  js  c++  java
  • 【Unity Shaders】学习笔记——SurfaceShader(九)Cubemap

    【Unity Shaders】学习笔记——SurfaceShader(九)Cubemap

    1. 如果你想从零开始学习Unity Shader,那么你可以看看本系列的文章入门,你只需要稍微有点编程的概念就可以。

    2. 水平有限,难免有谬误之处,望指出。


    上一节中讲述了制作Cubemap的方法。这一节讲讲怎么使用它。

    Simple Cubemap

    先来看一下最简单的Cubemap。

    Shader "Custom/SimpleReflection" 
    {
        Properties 
        {
            _MainTint ("Diffuse Tint", Color) = (1,1,1,1)
            _MainTex ("Base (RGB)", 2D) = "white" {}
            _Cubemap ("CubeMap", CUBE) = ""{}
            _ReflAmount ("Reflection Amount", Range(0.01, 1)) = 0.5
        }
        
        SubShader 
        {
            Tags { "RenderType"="Opaque" }
            LOD 200
            
            CGPROGRAM
            #pragma surface surf Lambert
    
            sampler2D _MainTex;
            samplerCUBE _Cubemap;
            float4 _MainTint;
            float _ReflAmount;
    
            struct Input 
            {
                float2 uv_MainTex;
                float3 worldRefl;
            };
    
            void surf (Input IN, inout SurfaceOutput o) 
            {
                half4 c = tex2D (_MainTex, IN.uv_MainTex) * _MainTint;
                o.Emission = texCUBE(_Cubemap, IN.worldRefl).rgb * _ReflAmount;
                o.Albedo = c.rgb;
                o.Alpha = c.a;
            }
            ENDCG
        } 
        FallBack "Diffuse"
    }
    

    其实就是用texCUBE()函数来对Cubemap采样。

    第二个参数应该传入UV坐标,但实际传入的是世界反射向量。这是Unity替我们计算了UV坐标。
    Cubemap是由六张贴图构成的,组成了一个类似天空盒的六面体,那这六张贴图是如何映射到小球上的呢,使小球看起来像倒映着周围的环境一样?
    将Cubemap想象成一个立方体,包裹着小球,从小球的中心点发射一条射线,穿过小球表面和立方体表面,射线与小球和立方体会各有一个交点,小球上的这个点对应的就是立方体上的这个点的纹理。那要怎样计算各个点对应的UV坐标呢?从小球中心点发射的一条条射线其实就是法线,有个很明显的事实就是,法线朝向法线坐标分量最大的坐标轴指向的立方体的面。也就是坐标(1,1,3)的法线朝向的是Z轴指向的立方体的面。这样就由法线找到了点对应的面。那么UV坐标又该如何计算呢?法线另外两个较小的坐标值和UV坐标是有关系的。举个特殊点的例子,小球最顶点的法线的坐标应该是(0,0,1),对应的应该是立方体的顶面的中点,UV坐标应该是(0.5,0.5)。计算UV坐标的方法是(x/z×0.5+0.5,y/z×0.5+0.5),将特殊点代进去,答案是正确的。原理有点难说明,相当于把X、Y坐标投影到了对应的面上。乘0.5加0.5是为了让法线坐标的区间从[-1,1]变为[0,1]。
    在Unity里,我们不必自己计算,可以直接使用内置变量worldRefl来检索Cubemap。

    Normal Cubemap

    Shader "Custom/NormalMappedReflection" 
    {
        Properties 
        {
            _MainTint ("Diffuse Tint", Color) = (1,1,1,1)
            _MainTex ("Base (RGB)", 2D) = "white" {}
            _NormalMap ("Normal Map", 2D) = "bump" {}
            _Cubemap ("Cubemap", CUBE) = ""{}
            _ReflAmount ("Reflection Amount", Range(0,1)) = 0.5
        }
        
        SubShader
         {
            Tags { "RenderType"="Opaque" }
            LOD 200
            
            CGPROGRAM
            #pragma surface surf Lambert
    
            samplerCUBE _Cubemap;
            sampler2D _MainTex;
            sampler2D _NormalMap;
            float4 _MainTint;
            float _ReflAmount;
    
            struct Input 
            {
                float2 uv_MainTex;
                float2 uv_NormalMap;
                float3 worldRefl;
                INTERNAL_DATA
            };
    
            void surf (Input IN, inout SurfaceOutput o) 
            {
                half4 c = tex2D (_MainTex, IN.uv_MainTex);
                
                float3 normals = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap)).rgb;
                o.Normal = normals;
                
                o.Emission = texCUBE (_Cubemap, WorldReflectionVector (IN, o.Normal)).rgb * _ReflAmount;
                o.Albedo = c.rgb * _MainTint;
                o.Alpha = c.a;
            }
            ENDCG
        } 
        FallBack "Diffuse"
    }
    

    就只是增加了法线贴图而已。
    因为法线信息的改变,所以要重新计算传入texCUBE()函数的世界反射向量。float3 worldRefl;INTERNAL_DATA变量用于原本的法线信息不使用时,比如使用了法线贴图,原来的法线信息就不使用了。要根据新的法线信息计算世界反射向量使用WorldReflectionVector()函数。

    动态立方图系统

    有时候游戏里的物体从一个环境走到另一个环境,需要更换Cubemap,这样显示的反射效果才真实。
    有两种方法更换Cubemap,一种是实时更换Cubemap,这样的效果最真实,但是要牺牲性能;第二种是当物体走到另一个环境的时候更换Cubemap,这就是我要讲的方法。
    方法很简单,就是在C#里用SetTexture的方法动态更换Cubemap。

    [ExecuteInEditMode]
    public class SwapCubemaps : MonoBehaviour 
    {
        public Cubemap cubeA;
        public Cubemap cubeB;
        
        public Transform posA;
        public Transform posB;
        
        private Material curMat;
        private Cubemap curCube;
        
    
        // Use this for initialization
        void Start () 
        {
        
        }
        
        // Update is called once per frame
        void Update () 
        {
            curMat = renderer.sharedMaterial;
            if(curMat)
            {
                curCube = CheckProbeDistance();
                curMat.SetTexture("_Cubemap", curCube);
                
            }
        }
        
        private Cubemap CheckProbeDistance()
        {
            float distA = Vector3.Distance(transform.position, posA.position);
            float distB = Vector3.Distance(transform.position, posB.position);
            
            if(distA < distB)
            {
                return cubeA;
            }
            else if(distB < distA)
            {
                return cubeB;
            }
            else
            {
                return cubeA;
            }
            
        }
            
        
        void OnDrawGizmos()
        {
            Gizmos.color = Color.green;
            
            if(posA)
            {
                Gizmos.DrawWireSphere(posA.position, 0.5f);
            }
            
            if(posB)
            {
                Gizmos.DrawWireSphere(posB.position, 0.5f);
            }
        }
    }
    
    1. [ExecuteInEditMode]是为了让脚本在编辑器状态的时候也能执行,这样就不必点击Play调试,比较方便。

    2. OnDrawGizmos()是在Scene里画一些可视化的东西方便调试。

    3. CheckProbeDistance()里用Distance判断物体和A点、B点的距离,根据距离决定返回哪种Cubemap。

    4. 在Update()设置材质的Cubemap。

    Cubemap效果

     

    Cubemap

    Cubemap

     

    我有加个金属的纹理。效果是这样的。

     

    NormalCubemap

    NormalCubemap

     

    这是加了法线贴图的。

  • 相关阅读:
    动手动脑
    大道至简第七八章读后感
    super 的用法
    第六章
    课后作业
    大道至简第五章读后感
    课后作业
    大道至简第四章读后感
    大道至简——第六章
    Java数组课后作业
  • 原文地址:https://www.cnblogs.com/-867259206/p/5656196.html
Copyright © 2011-2022 走看看