Created by miccall (转载请注明出处 miccall.tech)
1. Cull 剔除检查
在unity中,每个可以被渲染的物体,都带有一个Mesh Render组件
但是我们并不是渲染场景中每个物体,而是只渲染相机可以看到的那些物体。
所以我们从要剔除那些不在摄像机视锥之外的物体。
if (!CullResults.GetCullingParameters(camera, out var cullparamet)) return;
CullResults.GetCullingParameters 方法可以检测camera相机是否有效的剔除,
如果相机无效渲染(空视口矩形,无效剪裁平面设置等)那么我们直接return
否则,他会把剔除信息记录到 cullparamet 。
CullResults.Cull(ref cullparamet, context , ref _cull);
之后,调用 Cull 方法把物体剔除
然后,我们需要更新camera的一些view, projection and clipping planes等信息
context.SetupCameraProperties(camera);
此外 ,UI 层时unity为我们在render时增加的一层overlay ,我们在编辑器时需要对这一层做剔除
#if UNITY_EDITOR
if(camera.cameraType == CameraType.SceneView)
ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
#endif
2. Clear 清屏
清空颜色缓存和深度缓存
var clearFlags = camera.clearFlags;
_cameraBuffer.ClearRenderTarget(
(clearFlags & CameraClearFlags.Depth) != 0,
(clearFlags & CameraClearFlags.Color) != 0,
camera.backgroundColor
);
ExecuteCommandBuffer 开始执行 command buffer
_cameraBuffer.BeginSample("Render Camera");
context.ExecuteCommandBuffer(_cameraBuffer);
_cameraBuffer.Clear();
3. Draw Opaque 渲染不透明物体
渲染物体 ,首先根据渲染对象的类型 ,设置其 drawRendererSettings 的值
.sorting.flags 的值表示他渲染的物体排序方式
不透明物体的渲染时从前向后 ,而透明物体的渲染是从后向前
filterRendererSettings 表示渲染物体的种类
var drawRendererSettings = new DrawRendererSettings(camera, new ShaderPassName("SRPDefaultUnlit"))
{
sorting = {flags = SortFlags.CommonOpaque},
// 开启动态批处理
flags = _drawFlags
};
var filterRendererSettings = new FilterRenderersSettings(true)
{
renderQueueRange = RenderQueueRange.opaque
};
context.DrawRenderers(_cull.visibleRenderers,ref drawRendererSettings,filterRendererSettings);
context.DrawRenderers 来执行渲染操作
4. Sky box 渲染天空盒
context.DrawSkybox(camera);
5. DrawTransparent 渲染透明物体
同理,我们上面说到了不透明物体的渲染时从前向后 ,而透明物体的渲染是从后向前 。
这次我们同样修改 drawRendererSettings 和 filterRendererSettings ,
并且context.DrawRenderers来执行
drawRendererSettings.sorting.flags = SortFlags.CommonTransparent ;
filterRendererSettings.renderQueueRange = RenderQueueRange.transparent;
context.DrawRenderers(_cull.visibleRenderers,ref drawRendererSettings,filterRendererSettings);
6. error 错误材质
由于我们的管道仅支持我们自己的着色器,
而其他的却不会被渲染 。所有我们需要对其他进行统一处理,
最方便的方法就是使用错误的着色器。
让我们为此添加一个专用的DrawDefaultPipeline方法,
[Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")]
private void DrawDefaultPipeline(ScriptableRenderContext context, Camera camera)
{
if (_errorMaterial == null) {
var errorShader = Shader.Find("Hidden/InternalErrorShader");
_errorMaterial = new Material(errorShader) {
hideFlags = HideFlags.HideAndDontSave
};
}
var drawSettings = new DrawRendererSettings(
camera, new ShaderPassName("ForwardBase")
);
drawSettings.SetShaderPassName(1, new ShaderPassName("PrepassBase"));
drawSettings.SetShaderPassName(2, new ShaderPassName("Always"));
drawSettings.SetShaderPassName(3, new ShaderPassName("Vertex"));
drawSettings.SetShaderPassName(4, new ShaderPassName("VertexLMRGBM"));
drawSettings.SetShaderPassName(5, new ShaderPassName("VertexLM"));
drawSettings.SetOverrideMaterial(_errorMaterial,0);
var filterSettings = new FilterRenderersSettings(true);
context.DrawRenderers(
_cull.visibleRenderers, ref drawSettings, filterSettings
);
}
7. Dynamic Batching 动态合批处理
private readonly DrawRendererFlags _drawFlags;
// 开启 动态合批处理
if (dynamicBatching) {
_drawFlags = DrawRendererFlags.EnableDynamicBatching;
}
8. GPU Instance GPU 实例化
// 开启 GPU 实例化
if (instancing) {
_drawFlags |= DrawRendererFlags.EnableInstancing;
}
9. 材质
Shader "MyPipeLine/Unlit"
{
Properties
{
_Color("Color",Color) = (1,1,1,1)
}
SubShader
{
Pass
{
HLSLPROGRAM
#pragma target 3.5
#pragma multi_compile_instancing
#pragma instancing_options assumeuniformscaling
#pragma vertex UnlitPassVertex
#pragma fragment UnlitPassFragment
#include "../ShaderLibrary/Unlit.hlsl"
ENDHLSL
}
}
}
shader 包含了 vert 和 fragment shader
以及支持 instance 的 multi compile
此外 , 为支持instance 的矩阵变换 ,我们添加了 instancing_options
其余内容,添加到 hlsl 的include 文件
struct VertexInput {
float4 pos:POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct VertexOutput {
float4 clipPos:SV_POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
输入输出结构体
UNITY_VERTEX_INPUT_INSTANCE_ID 为支持instance 定义在 include 文件中
VertexOutput UnlitPassVertex (VertexInput input) {
VertexOutput output;
UNITY_SETUP_INSTANCE_ID(input);
UNITY_TRANSFER_INSTANCE_ID(input, output);
float4 worldPos = mul(UNITY_MATRIX_M, float4(input.pos.xyz, 1.0));
output.clipPos = mul(unity_MatrixVP, worldPos);
return output;
}
float4 UnlitPassFragment (VertexOutput input) : SV_TARGET {
UNITY_SETUP_INSTANCE_ID(input);
return UNITY_ACCESS_INSTANCED_PROP(PerInstance, _Color);
}
顶点着色器中,我们除了支持instace的定义外 ,
我们计算了 worldPos 和 clipPos
其中用到了 MVP 矩阵
在 片元着色器中 ,我们用color输出了 instance
矩阵的定义和赋值:
CBUFFER_START(UnityPerFrame)
float4x4 unity_MatrixVP;
CBUFFER_END
CBUFFER_START(UnityPerDraw)
float4x4 unity_ObjectToWorld;
CBUFFER_END
#define UNITY_MATRIX_M unity_ObjectToWorld
CBUFFER_START 是引用unity的常量缓存区,Constant buffer。
unity把矩阵计算好,存入其中
为了支持instace ,我们还需引入一个
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl"
UNITY_INSTANCING_BUFFER_START(PerInstance)
UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
UNITY_INSTANCING_BUFFER_END(PerInstance)
10. 灯光
平行光
点光
聚光
范围
衰减
// 最大支持的灯光数量
private const int maxVisibleLights = 16;
// 颜色id
private static int visibleLightColorsId = Shader.PropertyToID("_VisibleLightColors");
// 灯光的方向或者位置 (平行光忽略位置)
private static int visibleLightDirectionsOrPositionsId = Shader.PropertyToID("_VisibleLightDirectionsOrPositions");
// 灯光衰减
private static int visibleLightAttenuationsId = Shader.PropertyToID("_VisibleLightAttenuations");
// 聚光灯方向
private static int visibleLightSpotDirectionsId = Shader.PropertyToID("_VisibleLightSpotDirections");
//索引
private static int lightIndicesOffsetAndCountID = Shader.PropertyToID("unity_LightIndicesOffsetAndCount");
//buffer
private Vector4[] visibleLightColors = new Vector4[maxVisibleLights];
private Vector4[] visibleLightDirectionsOrPositions = new Vector4[maxVisibleLights];
private Vector4[] visibleLightAttenuations = new Vector4[maxVisibleLights];
private Vector4[] visibleLightSpotDirections = new Vector4[maxVisibleLights];
我们需要对这些赋值然后传入shader
// 可见性
if (_cull.visibleLights.Count > 0) ConfigureLights();
else _cameraBuffer.SetGlobalVector(lightIndicesOffsetAndCountID, Vector4.zero);
// 传入shader
_cameraBuffer.BeginSample("Render Camera");
// 设置 灯光 缓冲区
_cameraBuffer.SetGlobalVectorArray(
visibleLightColorsId, visibleLightColors
);
_cameraBuffer.SetGlobalVectorArray(
visibleLightDirectionsOrPositionsId, visibleLightDirectionsOrPositions
);
_cameraBuffer.SetGlobalVectorArray(
visibleLightAttenuationsId, visibleLightAttenuations
);
_cameraBuffer.SetGlobalVectorArray(
visibleLightSpotDirectionsId, visibleLightSpotDirections
);
context.ExecuteCommandBuffer(_cameraBuffer);
_cameraBuffer.Clear();
在 ConfigureLights(); 中 ,我们遍历场景中的灯光
private void ConfigureLights()
{
for (var i = 0; i < _cull.visibleLights.Count; i++)
{
// 灯光数量限制
if (i == maxVisibleLights) break;
// 可用灯光
var light = _cull.visibleLights[i];
//finalColor : 灯光的颜色乘以其强度
visibleLightColors[i] = light.finalColor;
// 衰减值
var attenuation = Vector4.zero;
attenuation.w = 1f;
// 平行光 : 计算灯光方向 ,位置无关
if (light.lightType == LightType.Directional)
{
var v = light.localToWorld.GetColumn(2);
v.x = -v.x;
v.y = -v.y;
v.z = -v.z;
visibleLightDirectionsOrPositions[i] = v;
}
else
{
// point light
visibleLightDirectionsOrPositions[i] = light.localToWorld.GetColumn(3);
attenuation.x = 1f / Mathf.Max(light.range * light.range, 0.00001f);
if (light.lightType == LightType.Spot)
{
var v = light.localToWorld.GetColumn(2);
v.x = -v.x;
v.y = -v.y;
v.z = -v.z;
visibleLightSpotDirections[i] = v;
var outerRad = Mathf.Deg2Rad * 0.5f * light.spotAngle;
var outerCos = Mathf.Cos(outerRad);
var outerTan = Mathf.Tan(outerRad);
var innerCos =
Mathf.Cos(Mathf.Atan((46f / 64f) * outerTan));
var angleRange = Mathf.Max(innerCos - outerCos, 0.001f);
attenuation.z = 1f / angleRange;
attenuation.w = -outerCos * attenuation.z;
}
}
visibleLightAttenuations[i] = attenuation;
}
if (_cull.visibleLights.Count <= maxVisibleLights) return;
{
var lightIndices = _cull.GetLightIndexMap();
for (var i = maxVisibleLights; i < _cull.visibleLights.Count; i++)
{
lightIndices[i] = -1;
}
_cull.SetLightIndexMap(lightIndices);
}
}