高級紋理以及複雜而真實的應用——ShaderCp10

——20.9.7app

這章主要分紅三個部分 立方體紋理(cubemap) 渲染紋理(RenderTexture,rt) 和程序紋理編輯器

1、立方體紋理函數

立方體紋理顧名思義是一種三維的紋理形狀相似於立方體,由六張圖像組成。能夠用於環境映射,能夠模擬物體周圍的環境,模擬金屬感。佈局

與以前的一維二維紋理採樣方式不一樣,立方體紋理咱們採用一個三維的紋理座標進行取樣。實際上這個三維的座標就是一個方向矢量。從立方體的中心出發,向外部延伸時與六張紋理之一相交計算得來的。優勢:快速方便,效果好。缺點:引入新的光源須要從新生成Cubemap,僅能反射環境不能反射該立方體紋理的物體自己。(不能進行模擬屢次反射)簡單的來講立方體紋理的創建就是從一個點向外繪製環境的紋理。因此一旦周圍環境的光源發生改變就須要從新繪製。性能

天空盒所用的就是立方體紋理。利用立方體紋理映射技術。咱們想讓不一樣的Camera用上不一樣的天空盒能夠給Camera添加Skybox組件來指定天空盒。spa

建立立方體紋理有三種方法 1.用一些特殊佈局的紋理(一張圖,具備立方體展開圖的交叉佈局,全景佈局等) 2.手動建立Cubemap 3.腳本生成紋理3d

這裏主要講第三種。由於前兩種靠資源。咱們要用Camera.RenderToCubemap 從任意的位置觀察到的場景存儲到六張圖像中。這裏將使用UnityEditor.ScriptableWizard編輯器嚮導來建立Cubemap建立界面。OnWizardCreate() : 點擊肯定按鈕調用此事件 OnWizardUpdate() : 當編輯器嚮導更新時調用orm

private void OnWizardCreate()
{
    GameObject go = new GameObject("CubemapCamera");
    go.AddComponent<Camera>();
    go.transform.position = renderFromPosition.position;
    go.GetComponent<Camera>().RenderToCubemap(cubemap);
    DestroyImmediate(go);
}

  這裏主要的方法是在渲染位置建立一個Cubemap的攝像機而後建立立方體紋理完後刪除。在編輯器界面建立能夠不用開始遊戲運行。cdn

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public class RenderCubemapWizard : ScriptableWizard
{
    public Transform renderFromPosition;
    public Cubemap cubemap;

    private void OnWizardUpdate()
    {
        helpString = "Select transform to render from and cubemap to render into";
        isValid = (renderFromPosition != null) && (cubemap != null);
    }

    private void OnWizardCreate()
    {
        GameObject go = new GameObject("CubemapCamera");
        go.AddComponent<Camera>();
        go.transform.position = renderFromPosition.position;
        go.GetComponent<Camera>().RenderToCubemap(cubemap);
        DestroyImmediate(go);
    }

    [MenuItem("GameObject/Render into Cubemap")]
    static void RenderCubemap()
    {
        ScriptableWizard.DisplayWizard<RenderCubemapWizard>("Render cuebemap", "Render!");
    }
}

  public static T DisplayWizard<T>(string title, string createButtonName)最後這個是建立的函數。就是對應標題以及按鈕。下面是建立的界面。blog

 

  而後就是怎麼對立方體紋理進行對應的應用。咱們能夠想想有了周圍的環境的紋理。能夠幹什麼呢?最主要就是環境的反射、折射等。來提高場景中物體的真實性。而後咱們就要來寫ShaderLab。我仔細思考了一下咱們應該從咱們應該設置什麼樣的變量。以及存在的定理入手來推導出須要什麼變量。而後怎麼運用。直接講裏面的代碼可能不是說特別好理解。下面由於使用的上面腳本生成的環境映射因此不是實時的。後面會介紹使用一些實時的方法。

咱們先實現對環境的反射。 咱們能夠想一下就是一個金屬的物體纔會有對應環境的反射。首先咱們有了Cubemap環境映射。咱們就須要體現金屬的金屬質感是強仍是弱。也就是拋光程度對應反射的強度。因此就有反射強度_ReflectAmount.而後就是原材質的顏色以及反射的顏色好比說環境由於材質反射的顏色不一樣。就有_Color以及_ReflectColor。而後就是主要代碼。

o.worldRefl = reflect(-o.worldViewDir, o.worldNormal); //vert
fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb * _ReflectionColor.rgb; //frag
fixed3 color = ambient + lerp(diffuse, reflection, _ReflectionAmount) * atten; //frag

  第一個是reflect函數這是在Cp6裏面用到的函數。當時所要解決的問題Phong光照模型須要反射角度。reflect(float3 i, float3 n) 對應的i是入射角度n是法線。返回反射方向。能夠想象咱們人眼是接受光線的地方,因此入射方向應該相反。也就是從環境的光發射到物體表面,根據物理咱們知道入射光和反射光角度一致。咱們實際上是在計算這個光路的過程。與咱們的人眼看的方向是逆方向。因此在前面加一個負號。兩個方向都是向外的!!!第二個是Cubemap的採樣結果。咱們沒有對其歸一化是由於是方向沒有必要。而咱們把反射的計算放在頂點着色器的緣由是雖然片元着色器可讓畫面更細膩但人眼以及分辨不出。節約性能。最後一個就是咱們使用lerp函數混合漫反射顏色以及反射顏色。而且乘以衰減值(點光源)。

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

Shader "Unlit/10-1Reflection"
{
    Properties
    {
		_Color ("Color", Color) = (1,1,1,1)
		_ReflectionColor ("ReflectionColor", Color) = (1,1,1,1)
		_ReflectionAmount ("_ReflectionAmount", Range(0,1)) = 1
		_Cubemap ("Cubemap", Cube) = "_Skybox"{}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Geometry"}
        Pass
        {
			Tags { "LightMode"="ForwardBase" }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
			#pragma multi_compile_fwdbase
            #include "UnityCG.cginc"
			#include "Lighting.cginc"
			#include "AutoLight.cginc"

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

            struct v2f
            {
                float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD1;
				float3 worldPos : TEXCOORD0;
				float3 worldViewDir : TEXCOORD2;
				float3 worldRefl : TEXCOORD3;
				SHADOW_COORDS(4)
            };

            fixed4 _Color;
			fixed4 _ReflectionColor;
			float _ReflectionAmount;
			samplerCUBE _Cubemap;

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex);
				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);

				fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb * _ReflectionColor.rgb;
				fixed3 diffuse = _LightColor0.rgb * _Color.rgb * saturate(dot(worldNormal, worldLightDir));
				fixed3 color = ambient + lerp(diffuse, reflection, _ReflectionAmount) * atten;
				return fixed4(color, 1.0);
            }
            ENDCG
        }
    }FallBack "Reflective/VertexLit"
}

  而後添加上從茶壺渲染的Cubemap。這就是對應的效果。

 

而後咱們來到了折射。有點像杯子(圓柱形)裏面有水。在杯子後面有一支筆。那透過水看到的筆會是什麼樣的呢?能夠先試一試能夠發現是鏡像的並且在中間發生比較大彎折。

咱們這裏要引出一個定理就是斯涅爾定理來計算折射角。

 定律中的角度分別是入射角以及折射角。對應的n一、n2是對應介質的折射率。因此當咱們有了公式那麼咱們須要什麼呢。就是n1/n2即_RefractRatio來表示折射率之比。還有就是折射強度_RefractAmount。以及對應的顏色_Color以及_RefractColor。以及咱們的環境映射_Cubemap。

o.worldRefr = refract(-normalize(o.worldViewDir), normalize(o.worldNormal), _RefractRatio); //vert
fixed3 refraction = texCUBE(_Cubemap, i.worldRefr).rgb * _RefractionColor.rgb; //frag
fixed3 color = ambient + lerp(diffuse, refraction, _RefractAmount) * atten; //frag

  其中重要的就是refract(float3 i, float3 n, float _RefractRatio) 對應的就是入射角、法線、以及入射介質折射率/折射介質折射率的比值。注意前面兩個變量須要歸一化。返回折射方向。第二個就是對應的texCube環境映射取樣。

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

Shader "Unlit/10-2Refraction"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
		_RefractionColor ("RefractionColor", Color) = (1,1,1,1)
		_RefractAmount ("RefractAmount", Range(0,1)) = 1
		_RefractRatio ("RefractRatio", Range(0.1,1)) = 0.5
		_Cubemap ("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"
            #include "UnityCG.cginc"

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

            struct v2f
            {
                float4 pos : SV_POSITION;
				float3 worldPos : TEXCOORD0;
				float3 worldNormal : TEXCOORD1;
				float3 worldViewDir : TEXCOORD2;
				float3 worldRefr : TEXCOORD3;
				SHADOW_COORDS(4)
            };

            fixed4 _Color;
			fixed4 _RefractionColor;
			float _RefractAmount;
			float _RefractRatio;
			samplerCUBE _Cubemap;

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex);
				o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
				o.worldRefr = refract(-normalize(o.worldViewDir), normalize(o.worldNormal), _RefractRatio);
				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 * saturate(dot(worldNormal, worldLightDir));
				fixed3 refraction = texCUBE(_Cubemap, i.worldRefr).rgb * _RefractionColor.rgb;
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
				fixed3 color = ambient + lerp(diffuse, refraction, _RefractAmount) * atten;
				return fixed4(color, 1.0);
            }
            ENDCG
        }
    }FallBack "Reflective/VertexLit"
}

  調整_RefractRatio到0.67就是水的折射率。效果以下。

 

 

 接下來就是最後一個應用——菲涅耳反射(Fresnel)。很是經常使用的反射而且應用至關普遍。根據視角方向控制反射程度。實際上在描述一種光學現象就是,當光線照射到物體上面,一部分光線會發生反射,一部分進入物體內部發生折射或是散射。被反射的光與入射光之間存在必定的比率關係。這個比率關係能夠經過菲涅耳進行計算。現實中的例子好比站在湖邊(海邊水可能不是很清澈)湖邊的水是能夠清澈見底的可是往湖中心看去卻看不見底。因而咱們就要引入公式,但由於菲涅耳等式是十分複雜的,因而咱們就用近似等式來代替。Schlick菲涅耳近似等式。

FSchlick(v, n) = F0 + (1 - F0)(1 - dot(v, n))5

對應的F0是強度、v是視角方向、n是法線方向。還有一個是Empricial 菲涅耳近似等式。

FEmpricial(v, n) = max(0, min(1, bias + scale * (1- dot(v, n)power)))

而後咱們主要用第一個公式。咱們須要強度_FresnelScale。以及_Color,和環境映射_Cubemap。

fixed fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(worldNormal, worldViewDir), 5); //frag
fixed3 color = ambient + lerp(diffuse, reflection, saturate(fresnel)) * atten; //frag

  這裏就要用近似公式計算反射的光(採用環境映射_Cubemap採樣結果的顏色)與入射光(漫反射光)的比例。而後用lerp來肯定程度。

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

Shader "Unlit/10-3Fresnel"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
		_FresnelScale ("FresnelScale", Range(0,1)) = 0.5
		_Cubemap ("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"
            #include "UnityCG.cginc"

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

            struct v2f
            {
                float4 pos : SV_POSITION;
				float3 worldPos : TEXCOORD0;
				float3 worldNormal : TEXCOORD1;
				float3 worldViewDir : TEXCOORD2;
				float3 worldRefl : TEXCOORD3;
				SHADOW_COORDS(4)
            };

            fixed4 _Color;
			float _FresnelScale;
			samplerCUBE _Cubemap;

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex);
				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);

				fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb;
				fixed fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(worldNormal, worldViewDir), 5);
				fixed3 diffuse = _LightColor0.rgb * _Color.rgb * saturate(dot(worldNormal, worldLightDir));
				fixed3 color = ambient + lerp(diffuse, reflection, saturate(fresnel)) * atten;
				return fixed4(color, 1.0);
            }
            ENDCG
        }
    }FallBack "Reflective/VertexLit"
}

  當_FresnelScale等於1的時候。是一個全反射的物體。當_FresnelScale等於0的時候是一個具備邊緣光照效果的漫反射物體。咱們能夠想象一下就是邊緣與視角的點乘結果,由於兩個方向近乎垂直。因此fresnel的值爲1也就是邊緣仍是全反射狀態(?單純本身運算想象)。後面一些有趣的邊緣操做就從這裏開始。

 

2、渲染紋理

 渲染目標紋理,把整個三維場景渲染到一箇中間緩衝中(不是幀緩衝/後備緩衝)。與之相關的是多重渲染目標,場景同時渲染到多個渲染目標紋理中。延遲渲染就是其中一種。

一般使用渲染紋理的兩個方法,1.Camera的Target Texture,rt將會實時的更新 2.GrabPass/OnRenderImage 獲取當前屏幕圖像,與當前屏幕分辨率相同。

概念有點多。有點沒有辦法理解。但下面這兩個效果我以前都有涉及過。第一個就是鏡子的效果。幾個要點就是這裏的鏡子效果十分的苛刻。(?後面我本身來研究研究這個)首先就是由於是鏡子也就是從裏面相對的位置有一個攝像機。把攝像機的內容傳到平面上。因此要新建一個攝像機在裏面。而後給他添加rt。而後我以前是把攝像機放在鏡子前面。而後就出現了下面這種無限套娃的狀況。問題出如今把鏡子也給渲染進去。有兩個解決方法。1.改變新攝像頭的ClippingPlanes讓它不渲染屏幕 2.設置鏡子的layer 並在新攝像機的CullingMask設置不渲染該層。但有沒有發現就是下面這個仍是錯的。由於水壺在左邊。但鏡子的在右邊。因此應該從鏡子背後去放置新的攝像機/這樣才能照到背後的圖像。

Shader "Unlit/10-4Mirror"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Geometry" }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"
			#include "Lighting.cginc"

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

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

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.texcoord;
                o.uv.x = 1 - o.uv.x;
                return o;
            }

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

  上面的shader主要就是水平翻轉圖像。

 

 而後就是玻璃效果。採用了GrabPass。在URP裏面不適用。在使用GrabPass的時候要當心渲染隊列。GrabPass一般渲染透明物體。雖然沒有混合操做。但也要把隊列設成Transparent。RenderType設成Opaque是爲了在使用着色器替換時能被正確渲染。

咱們同樣先從咱們須要什麼開始。首先是玻璃可能有對應的顏色。有色玻璃。那麼可能須要圖或者是色彩。咱們這裏用的是圖_MainTex。而後最重要的就是玻璃表面的凹凸感。凹凸感在前面Cp7裏面有說到就須要法線貼圖_BumpMap。以及變形強度_Distortion。最後還有就是透過玻璃的折射以及反射的比例_RefractAmount。

GrabPass { "_RefractionTex" } 
sampler2D _RefractionTex;
float4 _RefractionTex_TexelSize;
o.scrPos = ComputeGrabScreenPos(o.pos); //vert
//frag
float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
i.scrPos.xy = offset + i.scrPos.xy;
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);
fixed3 finalColor = reflCol * (1 - _RefractAmount) + refrCol * _RefractAmount;

  而後上面第一行就是GrabPass的聲明把屏幕截圖放到_RefractionTex裏面。而後同樣須要爲這個貼圖聲明變量。ComputeGrabScreenPos(留一個?我也不是很懂ComputeScreenPos以及它的區別)書上說是截取應被抓取的屏幕採樣座標。相似於肯定屏幕位置?而後下面就是_Distortion變形強度越大背後物體變形程度越大。_RefractionTex_TexelSize.xy是用來對屏幕座標進行偏移。而後咱們要把切線空間下的切線經過轉移矩陣轉移到世界空間。用於對_Cubemap採樣。最後就是反射以及折射的顏色比例。

 

 效果也是至關真實了。而後就是要不要聲明GrabPass,1.若是直接使用就會存在_GrabTexture。當場景中有多個物體使用GrabPass就會獲得不一樣的圖形。取決於他們的渲染隊列 2.聲明瞭就能夠一塊兒用。並且只用抓取一次 

GrabPass以及Rt二者相比rt的效率高。並且GrabPass不少狀況下不適用由於須要直接讀取Cpu後備緩衝。就破壞了Cpu和Gpu的並行性。就是順序是Cpu而後是Gpu。GrabPass是在Gpu的步驟。(?是這樣吧)因此接下來就是CommandBuffers的使用。(這裏留一個拓展研究?後面會另外寫一個來研究這個。由於這個是在腳本里面直接獲取的正確方式)

3、程序紋理

就是計算機生成圖像。這裏我沒有什麼理解就不寫了。有興趣的能夠繼續研究。但書裏的實在是不感興趣。

感謝你讀到這裏。又留下了不少坑。Cheers!

相關文章
相關標籤/搜索