自编渲染器Coocoo3D中的材质与渲染管线设计
这是对自编渲染器Coocoo3D的渲染管线设计的解析,交流图形编程技术。
你可以在此处找到Coocoo3D的完整源码 https://github.com/sselecirPyM/Coocoo3D
Coocoo3D的渲染管线
这是完全可编程的渲染管线,不包含任何系统预定义的对象,为快速实现原型设计,并且在性能和质量之间取得平衡。
渲染管线使用C#作为编程语言,大量使用了C#中的特性(Attribute),显著减少了编程所需的代码量。
接下来将介绍渲染管线的具体设计。
材质

Coocoo3D的材质不同于Unity,不能直接指定材质所使用的Shader。渲染时使用何种Shader由渲染管线决定。用户可以编写使用自定义着色器的渲染管线。
在渲染管线中声明字段并添加UIShowAttribute特性,可以直接在材质面板中显示。
[UISlider(0.0f, 1.0f, UIShowType.Material, "金属")]
public float Metallic;//此处为默认值,可在运行时修改
[UISlider(0.0f, 1.0f, UIShowType.Material, "粗糙")]
public float Roughness = 0.8f;//此处为默认值,可在运行时修改
[UIDragFloat(0.01f, 0, float.MaxValue, UIShowType.Material, "发光")]
public float Emissive;//此处为默认值,可在运行时修改
[UISlider(0.0f, 1.0f, UIShowType.Material, "高光")]
public float Specular = 0.5f;//此处为默认值,可在运行时修改
[UIShow(UIShowType.Material)]
[PureColorBaker(1, 1, 1, 1)]//为此纹理提供默认值
[Format(ResourceFormat.R8G8B8A8_UNorm)]//为此纹理提供默认值
[Size(32, 32)]//为此纹理提供默认值
[Srgb]
public Texture2D _Albedo;
资源管理
Coocoo3D的渲染资源由系统进行管理,用户代码不控制资源的创建、释放和修改。
[AOV(AOVType.Color)]//AOV是Arbitrary Output Variables的简写
[Size("Output")]//这个纹理的尺寸属于"Output"组,可以在每帧渲染开始前修改
[Format(ResourceFormat.R8G8B8A8_UNorm)]//此纹理的格式
public Texture2D output;//在尺寸、格式修改后仍然有效。
[AOV(AOVType.Depth)]
[Size("Output")]
[Format(ResourceFormat.D32_Float)]
[AutoClear]//每帧开始时清理此资源
public Texture2D depth;
public override void BeforeRender()
{
renderWrap.GetOutputSize(out int width, out int height);//RenderWrap是一个辅助操作类,此处使用它的实例。
renderWrap.SetSize("Output", width, height);//修改"Output"组的尺寸。
}
运行时烘焙
Coocoo3D使用特性标注的方法创建运行时烘焙任务,只要在想要烘焙的纹理成员上添加RuntimeBakeAttribute特性,系统就会在每帧开始时检查并运行烘焙。
用户可以创建自己的类型并继承自RuntimeBakeAttribute来添加自定义烘焙任务。
[UIShow(name: "天空盒")]
public Texture2D skyboxTexture;//输入的纹理
[Size(1024, 1024, 6)]
[Format(ResourceFormat.R16G16B16A16_Float)]
[CubeFrom2D(nameof(skyboxTexture))]//此类继承自RuntimeBakeAttribute,用途是将全景图转换为TextureCube
[BakeDependency(nameof(skyboxTexture))]//烘焙依赖项,每次重新设置天空盒纹理后依赖项失效,然后烘焙就会重新运行。
public TextureCube _SkyBox;//天空盒
[Size(512, 512, 6)]
[Format(ResourceFormat.R16G16B16A16_Float)]
[EnvironmentReflection(nameof(_SkyBox))]//此类继承自RuntimeBakeAttribute,用于烘焙IBL光照。
[BakeDependency(nameof(_SkyBox))]//表明此项依赖于天空盒,当天空盒失效时需要重新烘焙。
public TextureCube _Environment;//预过滤环境贴图
通过新建一个类并继承RuntimeBakeAttribute类来创建一个自动运行任务。
着色器
Coocoo3D使用原始的HLSL作为着色器语言,并且不使用代码生成以及着色器反射。Coocoo3D会在运行时编译着色器,并且可以通过宏定义的方式使用分支。何时使用何种着色器分支完全由用户编写的代码决定。
系统会自动缓存所使用的着色器,用户代码无需对此特殊处理。
传入着色器的参数由用户编写的代码控制,系统提供了辅助方法帮助自动控制或编程控制的参数传入,示例如下。
public class ForwordRenderPass//并不是真正的pass
{
public DrawObjectPass drawObject = new DrawObjectPass()
{
shader = "PBRMaterial.hlsl",//使用的着色器
renderTargets = new string[1],//初始化时赋值
depthStencil = null,//初始化时赋值
srvs = new string[] //将string[]参数传入渲染管线中,系统会通过名称自动匹配合适的资源,优先级 材质>用户定义>回退
{
"_Albedo",//来自材质
"_Metallic",//来自材质
"_Roughness",//来自材质
"_Emissive",//来自材质
"_ShadowMap",//来自用户定义
"_Environment",//来自用户定义
"_BRDFLUT",//来自用户定义
"_Normal",//来自材质
},
CBVPerObject = new object[] //将object[]传入渲染管线中,系统会自动匹配参数。
{
null,
"Metallic",
"Roughness",
"Emissive",
"Specular",
},
CBVPerPass = new object[]
{
nameof(ViewProjection),
nameof(View),
nameof(CameraPosition),
nameof(Brightness),
nameof(Far),
nameof(Near),
nameof(Fov),
nameof(AspectRatio),
nameof(ShadowMapVP),
nameof(ShadowMapVP1),
nameof(LightDir),
0,
nameof(LightColor),
0,
"SkyLightMultiple",
"FogColor",
"FogDensity",
"FogStartDistance",
"FogEndDistance",
},
};
}
在运行时用户代码可以调用着色器,例如DrawObjectPass类
public override void Execute(RenderWrap renderWrap)
{
renderWrap.SetRootSignature(rs);
renderWrap.SetRenderTarget(renderTargets, depthStencil, clearRenderTarget, clearDepth);
if (scissorViewport != null)
{
var rect = scissorViewport.Value;
renderWrap.SetScissorRectAndViewport(rect.Left, rect.Top, rect.Right, rect.Bottom);
}
var desc = GetPSODesc(renderWrap, psoDesc);
var writer = renderWrap.Writer;
writer.Clear();
if (CBVPerPass != null)
{
renderWrap.Write(CBVPerPass, writer);
writer.SetBufferImmediately(2);//设置CBV槽位2
}
keywords2.Clear();//重新利用容器
foreach (var renderable in renderWrap.MeshRenderables())
{
keywords2.AddRange(this.keywords);
if (filter != null && !filter.Invoke(renderWrap, renderable, keywords2)) continue;//使用过滤器跳过不需要渲染的部分
foreach (var keyMap in AutoKeyMap)//使用自动关键字
{
if (true.Equals(renderWrap.GetIndexableValue(keyMap.Item1, renderable.material)))
keywords2.Add((keyMap.Item2, "1"));
}
if (renderable.gpuSkinning)//Coocoo3D可以使用GPU蒙皮,也可以不使用GPU蒙皮。
{
keywords2.Add(new("SKINNING", "1"));
}
renderWrap.SetShader(shader, desc, keywords2, enableVS, enablePS, enableGS);//设置着色器,系统会自动编译所需的着色器变种。
if (renderable.gpuSkinning)
renderWrap.SetCBV(renderWrap.GetBoneBuffer(), 0);
CBVPerObject[0] = renderable.transform;
renderWrap.Write(CBVPerObject, writer, renderable.material);
writer.SetBufferImmediately(1);//设置CBV槽位1
renderWrap.SetSRVs(srvs, renderable.material);
renderWrap.Draw(renderable);
keywords2.Clear();
}
}
Pass
此渲染管线设计中没有Pass的概念,需要用户自行编写代码来实现Pass。
精心设计的代码可以极大的提高可读性,可以使用极少的代码实现功能。
public override void Render()
{
var camera = renderWrap.Camera;
forwordRenderPass.SetCamera(camera);
forwordRenderPass.Execute(renderWrap);
postProcess.Execute(renderWrap);
}
以上就是渲染管线设计的核心内容。

模型:Kizuna AI ©Kizuna AI, 天満宮風ステージ byムムム
前向渲染管线
此渲染管线的代码位于ForwardRenderPipeline.cs中,ForwardRenderPass.cs实现具体功能。
在ForwardRenderPass类里,有三个和渲染相关的字段:drawSkyBox、drawShadowMap、和drawObject。其中drawShadowMap和drawObject的类型是DrawObjectPass,在DrawObjectPass类里实现了遍历并渲染物体的功能。(Coocoo3D未实现剔除)
DrawObjectPass的AutoKeyMap字段允许这个pass自动的选择shader变体,如果ForwardRenderPipeline里含有被[Indexable]标记的字段并且值为true(这个值可以被材质覆盖),则会自动的为shader添加关键字。
AutoKeyMap =
{
("EnableFog","ENABLE_FOG"),
}
渲染部分,请看ForwardRenderPass类的Execute函数,此函数记录一些渲染所需的参数,然后运行渲染。
public void Execute(RenderWrap renderWrap)
{
drawSkyBox.renderTargets[0] = renderTarget;//设置pass的渲染目标
drawObject.renderTargets[0] = renderTarget;
drawObject.depthStencil = depthStencil;
var dls = renderWrap.directionalLights;
if (dls.Count > 0)//如果存在平行光,则渲染平行光照和阴影
{
ShadowMapVP = dls[0].GetLightingMatrix(InvertViewProjection, 0, 0.977f);
ShadowMapVP1 = dls[0].GetLightingMatrix(InvertViewProjection, 0.977f, 0.993f);
LightDir = dls[0].Direction;
LightColor = dls[0].Color;
drawObject.keywords.Add(("ENABLE_DIRECTIONAL_LIGHT", "1"));//添加关键字来使用含有光照的shader变体
}
else
{
ShadowMapVP = Matrix4x4.Identity;
ShadowMapVP1 = Matrix4x4.Identity;
LightDir = Vector3.UnitZ;
LightColor = Vector3.Zero;
}
renderWrap.PushParameters(this);//将此类被[Indexable]标记的字段也添加到参数中。
if (dls.Count > 0)//渲染平行光阴影
{
renderWrap.ClearTexture("_ShadowMap");
var shadowMap = renderWrap.GetRenderTexture2D("_ShadowMap");
drawShadowMap.CBVPerObject[1] = ShadowMapVP;
drawShadowMap.scissorViewport = new Rectangle(0, 0, shadowMap.width / 2, shadowMap.height / 2);
drawShadowMap.Execute(renderWrap);
drawShadowMap.CBVPerObject[1] = ShadowMapVP1;
drawShadowMap.scissorViewport = new Rectangle(shadowMap.width / 2, 0, shadowMap.width / 2, shadowMap.height / 2);
drawShadowMap.Execute(renderWrap);
}
drawSkyBox.Execute(renderWrap);//渲染天空盒
drawObject.Execute(renderWrap);//渲染物体
renderWrap.PopParameters();//移除参数覆盖
drawObject.keywords.Clear();//清除之前添加的关键字
}

浙公网安备 33010602011771号