天天看點

Unity SRP自定義渲染管線學習2.1: DrawCalls 編寫無光物體Shader寫在前面軟體環境編寫ShaderSwizzle operation使用Core RP Library添加顔色屬性參考

寫在前面

要繪制出物體,CPU必須告訴GPU繪制什麼,怎麼繪制?通常Mesh控制了繪制什麼,Shader則控制怎麼繪制,會給GPU一系列的設定。除了Mesh外,Shader需要額外的資訊,包括物體的空間資訊矩陣,材質屬性等。

Unity的通用渲染管線和高清渲染管線中,可以通過Shader Graph插件來編輯Shader,但是如果是我們自己定義的渲染管線,我們必須自己寫Shader代碼,自己寫的話,我們能夠完全控制并且了解好Shader所要做的工作。

軟體環境

我使用的Unity版本:Unity2020.3.10f1

編寫Shader

我們建立第一個無光隻有純色的Shader,建立後先清空所有的内容

Unity SRP自定義渲染管線學習2.1: DrawCalls 編寫無光物體Shader寫在前面軟體環境編寫ShaderSwizzle operation使用Core RP Library添加顔色屬性參考

要繪制一個Mesh,GPU必須将Mesh的面進行光栅化,轉換成像素資料,通過将頂點坐标從3維空間轉換到2維的視圖空間,再填入像素資料。這兩步就是通過我們自己定義的頂點函數(vertex kernel/program/shader),片元函數(fragment kernel/program/shader)。一個片元對應着顯示一個像素或者是紋理像素,但可能不是最終顯示的,因為它的上面還可能有其他物體會把它給覆寫掉。

我們先在Unlit Shader中寫好基本結構

Shader "Custom RP/Unlit"
{
    Properties {}
    SubShader {
        Pass {
            HLSLPROGRAM  //CGPROGRAM 和 HLSLPROGRAM 都是支援的,但我們看現在的Unity官方的渲染管線都是這麼寫的了
            //pragma來源于希臘語,指需要做某些事或者一個操作,在很多的程式設計語言中都用于表示特殊的編譯器指令,對,就是指令,或許中文的“指令”一詞能比較好的代表其的含義
            #pragma vertex UnlitPassVertex
            #pragma fragment UnlitPassFragment
            #include "UnlitPass.hlsl"  //對應着.cginc,和上面的HLSLPROGRAM一個道理
            
            ENDHLSL
        }
    }
}

           

我們把一些方法放在UnlitPass.hlsl中,和之前的.cginc是一個道理

Unity SRP自定義渲染管線學習2.1: DrawCalls 編寫無光物體Shader寫在前面軟體環境編寫ShaderSwizzle operation使用Core RP Library添加顔色屬性參考

先簡單實作我們需要的頂點、片元函數

#ifndef CUSTOM_UNLIT_PASS_INCLUDED  //避免被多個檔案include,導緻重複定義,導緻編譯錯誤,基本上頭檔案開頭都得這樣子寫
#define CUSTOM_UNLIT_PASS_INCLUDED

/* 輸入的坐标為什麼定義為float3,而不是float4
點坐标和方向向量都被定義為4維坐标,點坐标的w=1,方向向量的w=0,這是為了能夠直接對坐标和方向進行矩陣變換。
是以我們定義坐标和方向向量時,如果不需要混合運算的話,我們可以先隻定義3維向量,比如把輸入資料定義為float3,在最後加上1或0即可,這樣可以節約開銷。
*/
float4 UnlitPassVertex(float3 positionOS : POSITION) : SV_POSITION  //語義SV_POSITION指傳回值是位置坐标,語義需要指出傳回的值的用途
{
    //positionOS是物體空間坐标,Object Space
    return float4(positionOS, 1.0);
    // return 0.0;  
}
/*  關于Shader值類型使用float、half、fixed的糾結說明
大部分的移動裝置的GPU支援兩種精度類型:float和half,half類型性能更優,是以如果在寫移動端的shader時可以盡可能的使用half。
根據經驗,僅僅位置、紋理坐标需要使用float類型,其他的都可以使用half類型,結果相差無幾
但如果不是移動裝置,使用哪種精度類型就無所謂了,因為即使我們使用half,實際上GPU用的也是float
而對于fixed類型,隻有在一些老的移動裝置中才會支援,現在的裝置基本都不再支援,是以即使寫的是fixed,實際上也是用half
*/
float4 UnlitPassFragment() : SV_TARGET  //語義SV_TARGET指傳回值是用于RenderTarget的
{
    return 0.0;  //傳回一個值會直接被轉換成(0.0, 0.0, 0.0, 0.0),因為是不透明物體,是以alpha通道為0也沒關系
}
#endif

           

我們先嘗試了一下在頂點函數中直接傳回物體空間坐标,錯誤表現

Unity SRP自定義渲染管線學習2.1: DrawCalls 編寫無光物體Shader寫在前面軟體環境編寫ShaderSwizzle operation使用Core RP Library添加顔色屬性參考

仿照Unity官方管線的做法,添加一個ShaderLibrary目錄,添加一個UnityInput檔案,用來存放一些通用的方法,比如馬上要寫的轉換空間坐标的方法

Unity SRP自定義渲染管線學習2.1: DrawCalls 編寫無光物體Shader寫在前面軟體環境編寫ShaderSwizzle operation使用Core RP Library添加顔色屬性參考
#ifndef CUSTOM_UNITY_INPUT_INCLUDED
#define CUSTOM_UNITY_INPUT_INCLUDED
float4x4 unity_ObjectToWorld;  //每一次繪制GPU設定這個值,然後在一次繪制中的頂點片元函數使用期間值不變
float4x4 unity_MatrixVP;  //View-Projection Matrix

#endif

           

Common.hlsl

Unity SRP自定義渲染管線學習2.1: DrawCalls 編寫無光物體Shader寫在前面軟體環境編寫ShaderSwizzle operation使用Core RP Library添加顔色屬性參考
#ifndef CUSTOM_COMMON_INCLUDED
#define CUSTOM_COMMON_INCLUDED
#include "UnityInput.hlsl"
float3 TransformObjectToWorld(float3 positionOS)
{
    //Swizzle operation: In computer graphics, swizzling is the ability to compose vectors by arbitrarily rearranging and combining components of other vectors.[1] For example, if A = {1,2,3,4}, where the components are x, y, z, and w respectively, you could compute B = A.wwxy, whereupon B would equal {4,4,1,2}.
    return mul(unity_ObjectToWorld, float4(positionOS, 1.0)).xyz;
    // return 0.0;
}

float4 TransformWorldToHClip(float3 positionWS)
{
    return mul(unity_MatrixVP, float4(positionWS, 1.0));
}

#endif
           

然後我們就可以在頂點片元函數中使用這些方法了

UnlitPass.hlsl

float4 UnlitPassVertex(float3 positionOS : POSITION) : SV_POSITION  //語義SV_POSITION指傳回值是位置坐标,語義需要指出傳回的值的用途
{
    //positionOS是物體空間坐标,Object Space
    float3 positionWS = TransformObjectToWorld(positionOS.xyz);
    return TransformWorldToHClip(positionWS);
    // return float4(positionWS, 1.0);
    // return 0.0;  
}

           

當我們在頂點函數最後輸出的是裁剪空間坐标後大小就正确了

Unity SRP自定義渲染管線學習2.1: DrawCalls 編寫無光物體Shader寫在前面軟體環境編寫ShaderSwizzle operation使用Core RP Library添加顔色屬性參考

Swizzle operation

什麼是Swizzle operation或者Swizzling,就是通過任意組合排列一個向量的元素得到另外一個向量的操作,比如A = {1, 2, 3, 4},我們讓B = A.wxxx,那麼 B = {4, 1, 1, 1},這個操作就叫Swizzle operation或者Swizzling

Wiki上原文:

In computer graphics, swizzling is the ability to compose vectors by arbitrarily rearranging and combining components of other vectors.[1] For example, if A = {1,2,3,4}, where the components are x, y, z, and w respectively, you could compute B = A.wwxy, whereupon B would equal {4,4,1,2}.

貓神原文:

Unity SRP自定義渲染管線學習2.1: DrawCalls 編寫無光物體Shader寫在前面軟體環境編寫ShaderSwizzle operation使用Core RP Library添加顔色屬性參考

使用Core RP Library

實際上我們還可以直接使用Core RP Library,其中有很多已經定義好的通用方法

Unity SRP自定義渲染管線學習2.1: DrawCalls 編寫無光物體Shader寫在前面軟體環境編寫ShaderSwizzle operation使用Core RP Library添加顔色屬性參考

UnlitPass.hlsl

#ifndef CUSTOM_UNLIT_PASS_INCLUDED  //避免被多個檔案include,導緻重複定義,導緻編譯錯誤,基本上頭檔案開頭都得這樣子寫
#define CUSTOM_UNLIT_PASS_INCLUDED
#include "../ShaderLibrary/Common.hlsl" //不是一個目錄的,需要使用相對路徑

           

Common.hlsl

#ifndef CUSTOM_COMMON_INCLUDED
#define CUSTOM_COMMON_INCLUDED
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "UnityInput.hlsl"
#define UNITY_MATRIX_M unity_ObjectToWorld  //所有的UNITY_MATRIX_M會被替換成unity_ObjectToWorld,Unity的Package中用的是UNITY_MATRIX_M
#define UNITY_MATRIX_I_M unity_WorldToObject
#define UNITY_MATRIX_V unity_MatrixV
#define UNITY_MATRIX_VP unity_MatrixVP
#define UNITY_MATRIX_P glstate_matrix_projection
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/SpaceTransforms.hlsl"  //直接将Package的檔案包含進來
/* 這兩個方法實際上在Core RP Library中已經有定義了,我們可以直接使用Package
float3 TransformObjectToWorld(float3 positionOS)
{
    //Swizzle operation: In computer graphics, swizzling is the ability to compose vectors by arbitrarily rearranging and combining components of other vectors.[1] For example, if A = {1,2,3,4}, where the components are x, y, z, and w respectively, you could compute B = A.wwxy, whereupon B would equal {4,4,1,2}.
    return mul(unity_ObjectToWorld, float4(positionOS, 1.0)).xyz;
    // return 0.0;
}
float4 TransformWorldToHClip(float3 positionWS)
{
    return mul(unity_MatrixVP, float4(positionWS, 1.0));
}
*/
#endif

           

UnityInput.hlsl

#ifndef CUSTOM_UNITY_INPUT_INCLUDED
#define CUSTOM_UNITY_INPUT_INCLUDED
float4x4 unity_ObjectToWorld;  //每一次繪制GPU設定這個值,然後在一次繪制中的頂點片元函數使用期間值不變
float4x4 unity_WorldToObject;
real4 unity_WorldTransformParams;
float4x4 unity_MatrixVP;  //View-Projection Matrix
float4x4 unity_MatrixV;
float4x4 glstate_matrix_projection;
#endif

           

添加顔色屬性

我們添加顔色的屬性變量

Unlit.shader

Properties {
        _BaseColor("Color", Color) = (1.0, 1.0, 1.0, 1.0)
    }

           

UnlitPass.hlsl

float4 _BaseColor;  //用于Shader中定義顔色屬性,名稱需相同


float4 UnlitPassFragment() : SV_TARGET  //語義SV_TARGET指傳回值是用于RenderTarget的
{
    return _BaseColor;
    // return float4(1.0, 1.0, 0.0, 1.0);
    // return 0.0;  //傳回一個值會直接被轉換成(0.0, 0.0, 0.0, 0.0),因為是不透明物體,是以alpha通道為0也沒關系
}

           

這樣我們就可以調整顔色了

Unity SRP自定義渲染管線學習2.1: DrawCalls 編寫無光物體Shader寫在前面軟體環境編寫ShaderSwizzle operation使用Core RP Library添加顔色屬性參考

參考

本文主要學習自: https://catlikecoding.com/unity/tutorials/custom-srp/draw-calls/

https://bitbucket.org/catlikecodingunitytutorials/custom-srp-02-draw-calls/src/master/

catlikecoding是大神的部落格,裡面有很多教程,膜拜大神,感恩大神。

繼續閱讀