GPUInstancing的实现原理

879 阅读2分钟

GPUInstancing的实现原理

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。

作为一名优秀的开发者,对于技术的探求应该是永无止步的,如何让自己更进步,那就只有不停的学习,不停的充电.而这些都都是说起来容易做起来难,坚持才是难的地方 --蛙哈哈

GPU Instancing 其实是 Unity 对 GPU 渲染命令的上层封装,将它变得简单好用.

在OpenGL中的名字叫做:多实例渲染接口

比如 OpenGL 提供了一些接口:

void glDrawArraysInanced(GLenum mode, GLint first, GLsizei count, Glsizei primCount);//无索引的顶点网格集多实例渲染

void glDrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei primCount);//索引网格的多实例渲染

void glDrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instanceCount, GLuint baseVertex);//索引基于偏移的网格多实例渲染

GPU Instancing 有两个特性:

  1. 多实例渲染
  2. 每个实例可以有自己的特色,比如不同的缩放/颜色/位置等等

如何做到多实例的渲染呢?

先看 OpenGL 的接口, 以第一个接口为例:

通过mode、first和count所构成的几何体图元集,绘制 primCount 个实例,置变量 gl_InstanceID 都会依次递增,新的数值会被传递到顶点着色器,以区分不同实例的顶点属性.

这个函数是 glDrawArrays() 的多实例版本.

void glDrawArrays(  GLenum mode,    GLint first,    GLsizei count);  

每次 OpenGL调用 glDrawArraysInanced 时,其实调用的是 glDrawArrays 的 primCount 次拷贝, 每次的 mode、first 和 count 参数都是直接传入的, 模型网格数据都是同一个,只要让GPU进行多次绘制就可以了,没有CPU啥事,所以就提高的渲染的效率.

如何做到个性化呢?

再看Untiy的Shader文件, 这个文件是Unity文档中的简单的一个 多实例渲染的Shader,下面会进行解释.

Shader "SimplestInstancedShader"
{
    Properties
    {
        _Color ("Color", Color) = (1, 1, 1, 1)
    }

    SubShader
    {
        ...

        Pass
        {
            CGPROGRAM
           	...
            #pragma multi_compile_instancing // 开启多实例的变量编译
            ...

            struct appdata
            {
                float4 vertex : POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID //顶点着色器的 InstancingID定义
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID //片元着色器的 InstancingID定义
            };

            UNITY_INSTANCING_BUFFER_START(Props)
                UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
            UNITY_INSTANCING_BUFFER_END(Props)
           
            v2f vert(appdata v)
            {
                v2f o;

                UNITY_SETUP_INSTANCE_ID(v); //装配 InstancingID
                UNITY_TRANSFER_INSTANCE_ID(v, o); //输入到结构中传给片元着色器

                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
           
            fixed4 frag(v2f i) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(i); //装配 InstancingID
                return UNITY_ACCESS_INSTANCED_PROP(Props, _Color); //提取多实例中的当前实例的Color属性变量值
            }
            ENDCG
        }
    }
}

#pragma multi_compile_instancing

告诉着色器生成Instancing变体

UNITY_VERTEX_INPUT_INSTANCE_ID //顶点着色器的 InstancingID定义

之所以能进行个性化绘制,就是着色器每次都知道现在绘制的是哪一个实例, InstancingID 就是实例的编号,是与实例对应的数组的索引.

image-20220123183036464.png

在UnityInstancing.cginc文件中,可以看到 UNITY_VERTEX_INPUT_INSTANCE_ID 的定义

uint instanceID : SV_InstanceID;

SV_InstanceID的语义就是: 由运行时自动生成的每个实例标识符.

因为篇幅的问题,关于如何使用使用这个InstanceID,可以看下一篇.