天天看點

初識Unity SRP

Unity SRP 即 Unity Scriptable Rendering Pipeline(可程式設計渲染管線),是Unity 2018的新功能,使開發者可以通過腳本按需建構自己的渲染過程。在學習和參考:

吉祥的遊戲程式設計筆記

中關于Unity SRP的相關内容後,這裡做一個簡單的學習記錄,如有錯誤之處,希望可以多多交流。

SRP中的内容可以用一張圖說明: 

初識Unity SRP

SRP的建立過程分為3個部分:

  • Custom Render Pipeline
  • Custom Render Pipeline Asset
  • Shader

Custom Render Pipeline

該部分是自定義渲染管線的起點,也是核心部分,繼承自RenderPipeline類。在該類中的Render()方法中,定義自己的渲染流程中的規則及進行參數設定。一般來說,渲染的過程是将錄影機視野中3D或2D的場景對象進過一系列的處理最後轉換成一張2D的圖檔輸出到螢幕上。是以在Render()方法中,處理起點是從編輯場景裡的每一個輸出相機開始,經過如下過程:

  • 設定渲染目标
  • 繪制天空盒
  • 執行裁剪過程
  • 執行過濾過程
  • 繪制場景準備
  • 執行管線

在這一系列的過程中,需要将相關的指令和設定送出,通過 CommandBuffer和ScriptableRenderContext對象實作。 CommandBuffer對象作為指令緩存集,記錄部分指令然後集中送出。ScriptableRenderContext對象可以了解成渲染過程中的一個管理器,CommandBuffer對象的指令送出需要通過ScriptableRenderContext對象完成,同時ScriptableRenderContext對象還可以管理如天空球的繪制、下達執行管線渲染等操作。

Custom Render Pipeline 完整代碼:

using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;


namespace Kata03 {
public class CustomRenderPipeline : RenderPipeline
{
    CommandBuffer _cb;

    //該函數在管線銷毀時調用
    public override void Dispose()
    {
        base.Dispose();
        if (_cb != null) {
            _cb.Clear();
            _cb = null;
        }
    }

    //該函數在管線渲染時調用
    public override void Render(ScriptableRenderContext renderContext, Camera[] cameras)
    {
        base.Render(renderContext, cameras);

        if (_cb == null) {
            _cb = new CommandBuffer();
        }

        //設定Shader中要使用的光源變量名
        var _LightDir = Shader.PropertyToID("_LightDir");
        var _LightColor = Shader.PropertyToID("_LightColor");
        var _CameraPos = Shader.PropertyToID("_CameraPos");

        //對于每個相機執行的操作
        foreach (var camera in cameras)
        {
            //設定渲染上下文相機屬性
            renderContext.SetupCameraProperties(camera);

            _cb.name = "Setup";
            //顯式設定渲染目标為相機BackBuffer(如果相機沒有指定渲染紋理,則直接繪制到螢幕)
            _cb.SetRenderTarget(BuiltinRenderTextureType.CameraTarget);
            //設定渲染目标顔色為相機背景色
            _cb.ClearRenderTarget(true, true, camera.backgroundColor);

            //設定相機的着色器全局變量
            Vector4 CameraPosition = new Vector4(camera.transform.localPosition.x, camera.transform.localPosition.y, camera.transform.localPosition.z, 1.0f);
            _cb.SetGlobalVector(_CameraPos, camera.transform.localToWorldMatrix * CameraPosition);
            renderContext.ExecuteCommandBuffer(_cb);
            _cb.Clear();

            //天空盒繪制
            renderContext.DrawSkybox(camera);

            //執行裁剪
            var culled = new CullResults();
            CullResults.Cull(camera, renderContext, out culled);

            /*
             裁剪結果包括:
                可見的物體清單:visibleRenderers
                可見燈光清單:visibleLights
                可見反射探針(CubeMap):visibleReflectionProbes
             裁剪結果并未排序
             */

            //擷取所有燈光
            var lights = culled.visibleLights;
            _cb.name = "RenderLights";
            foreach (var light in lights)
            {
                //挑選出平行光處理
                if (light.lightType != LightType.Directional) continue;
                //擷取光源方向
                Vector4 pos = light.localToWorld.GetColumn(0);
                Vector4 lightDir = new Vector4(pos.x,pos.y,pos.z,0);
                //擷取光源顔色
                Color lightColor = light.finalColor;
                //建構shader常量緩存
                _cb.SetGlobalVector(_LightDir,lightDir);
                _cb.SetGlobalColor(_LightColor,lightColor);
                renderContext.ExecuteCommandBuffer(_cb);
                _cb.Clear();

                var rs = new FilterRenderersSettings(true);
                //隻渲染固體範圍
                rs.renderQueueRange = RenderQueueRange.opaque;
                //包括所有層
                rs.layerMask = ~0;

                //渲染設定,使用Shader中LightMode為"BaseLit"的Pass
                var ds = new DrawRendererSettings(camera,new ShaderPassName("BaseLit"));
                //物體繪制
                renderContext.DrawRenderers(culled.visibleRenderers,ref ds,rs);

                break;
            }

            //開始執行管線
            renderContext.Submit();
        }
    }
  }
}
           

Custom Render Pipeline Asset

Custom Render Pipeline Asset 繼承自 Render Pipeline Asset,用來在項目中生成自定義渲染管線資源,主要實作 IRenderPipelineAsset接口方法, InternalCreatePipeline(),生成自定義的 Custom RenderPipeline 對象,完整代碼:

using UnityEngine.Experimental.Rendering;

#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.ProjectWindowCallback;
#endif

namespace Kata03 {

public class CustomRenderPipelineAsset : RenderPipelineAsset
{
#if UNITY_EDITOR
    [MenuItem("Assets/Create/Render Pipeline/Kata03/Pipeline Asset")]
    static void CreateKata03Pipeline() {
        ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0,CreateInstance<CreateKata03PipelineAsset>(),"Kata03 Pipeline.asset",null,null);
    }
    class CreateKata03PipelineAsset : EndNameEditAction
    {
        public override void Action(int instanceId, string pathName, string resourceFile)
        {
            var instance = CreateInstance<CustomRenderPipelineAsset>();
            AssetDatabase.CreateAsset(instance,pathName);
        }
    }

#endif

    protected override IRenderPipeline InternalCreatePipeline()
    {
        return new CustomRenderPipeline();
    }
  }
}
           

有了 Custom Render Pipeline Asset 後,在Projects中通過 Create->RdnerPipeline->Kata03->Pipeline 即可建立對應的自定義渲染管線資源,然後替換原有的管線,在Edit->Project->Settings->Graphics中的Scriptable Render PipelineLine Settings中完成替換。

Shader

Shader是配合目前的自定義管線,完成場景内的物體着色,這和之前使用Unity内置管線沒有差別。隻是這裡有一點需要注意的是:

在CustomRenderPipeline的渲染設定中,

var ds = new DrawRendererSettings(camera,new ShaderPassName("BaseLit"));
           

這裡在建構ShaderPassName對象時,傳遞的參數為使用的Shader的Pass中"LightMode"對應的名稱,因為在自定義渲染管線中,場景内物體光照着色的區分是通過Shader内的Pass裡的"LightMode"進行的。包含光照計算的Shader代碼:

Shader "Custom/BaseDirLit"
{
Properties
{
	_Color("Tint", Color) = (0.5,0.5,0.5,1)
	_DiffuseFactor("Diffuse Factor", Range(0,1)) = 1
	_SpecularColor("Specular Color",Color)=(1,1,1,1)
	_SpecularFactor("Specular Factor", Range(0,1)) = 1
	_SpecularPower("Specular Power",Float) = 100
}

HLSLINCLUDE

#include "UnityCG.cginc"
#define PI 3.14159265359

uniform float4 _LightDir;
uniform float4 _LightColor;
uniform float4 _CameraPos;
uniform float4 _Color;
uniform float _DiffuseFactor;
uniform float _SpecularFactor;
uniform float _SpecularColor;
uniform float _SpecularPower;

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

struct v2f
{
	float4 pos : SV_POSITION;
	float4 normalWorld : TEXCOORD1;
	float4 worldPos : TEXCOORD2;
};

v2f vert(a2v v)
{
	v2f o;
	UNITY_INITIALIZE_OUTPUT(v2f,o);
	o.pos = UnityObjectToClipPos(v.vertex);
	o.normalWorld = float4(normalize(mul(normalize(v.normal.xyz), (float3x3)unity_WorldToObject)),v.normal.w);
	o.worldPos = mul(unity_ObjectToWorld,v.vertex);
	return o;
}

half4 frag(v2f i) : SV_Target
{
	fixed4 diffuse=_DiffuseFactor*max(0.0,dot(_LightDir.xyz,normalize(i.normalWorld.xyz)))*_Color*_LightColor;
	fixed3 viewDir=normalize(_CameraPos.xyz-i.worldPos.xyz);
	fixed3 halfDir=normalize(_LightDir.xyz+viewDir);
	fixed4 specular= _LightColor*_SpecularFactor*pow(max(0,dot(normalize(i.normalWorld.xyz),halfDir)),_SpecularPower)*max(0,saturate(dot(normalize(i.normalWorld.xyz),_LightDir.xyz)))*(_SpecularPower+8)/(8+PI);
	return diffuse+specular;
}

ENDHLSL

SubShader
{
	Tags{ "Queue" = "Geometry" }
	LOD 100
	Pass
	{
		Tags {"LightMode" = "BaseLit"}

		HLSLPROGRAM
		#pragma vertex vert
		#pragma fragment frag

		ENDHLSL
	}
  }
}
           

在場景中使用該Shader的材質,得到的對應效果:

初識Unity SRP

繼續閱讀