Version: 2020.3

Graphics.DrawMeshInstancedIndirect

切换到手册
public static void DrawMeshInstancedIndirect (Mesh mesh, int submeshIndex, Material material, Bounds bounds, ComputeBuffer bufferWithArgs, int argsOffset= 0, MaterialPropertyBlock properties= null, Rendering.ShadowCastingMode castShadows= ShadowCastingMode.On, bool receiveShadows= true, int layer= 0, Camera camera= null, Rendering.LightProbeUsage lightProbeUsage= LightProbeUsage.BlendProbes, LightProbeProxyVolume lightProbeProxyVolume= null);
public static void DrawMeshInstancedIndirect (Mesh mesh, int submeshIndex, Material material, Bounds bounds, GraphicsBuffer bufferWithArgs, int argsOffset= 0, MaterialPropertyBlock properties= null, Rendering.ShadowCastingMode castShadows= ShadowCastingMode.On, bool receiveShadows= true, int layer= 0, Camera camera= null, Rendering.LightProbeUsage lightProbeUsage= LightProbeUsage.BlendProbes, LightProbeProxyVolume lightProbeProxyVolume= null);

参数

mesh 要绘制的 Mesh
submeshIndex 要绘制网格的哪个子集。这只适用于由若干种材质构成的网格。
material 要使用的 Material
bounds 围绕要绘制的实例的包围体。
bufferWithArgs GPU 缓冲区包含相应的参数,指示要绘制此网格的实例数。
argsOffset 缓冲区中的字节偏移,绘制参数起始位置。
properties 要应用的其他材质属性。请参阅 MaterialPropertyBlock
castShadows 确定网格是否可以投射阴影。
receiveShadows 确定网格是否可以接受阴影。
layer 要使用的 Layer
camera If null (default), the mesh will be drawn in all cameras. Otherwise it will be drawn in the given Camera only.
lightProbeUsage 实例的 LightProbeUsage

描述

Draws the same mesh multiple times using GPU instancing.

类似于 Graphics.DrawMeshInstanced,此函数用于绘制同一网格的多个实例,但与之不同的是,此函数中代表要绘制实例数的参数来自 /bufferWithArgs/。

如果您需要使用实例化的着色器多次绘制同一网格,则可以使用该函数。这些网格不会被视锥体或烘焙遮挡物做进一步的剔除处理,也不进行排序以提高透明度或 Z 效率。

带参数的缓冲区 bufferWithArgs 必须在给定的 argsOffset 偏移处具有五个整数: 每个实例的索引数、实例数、起始索引位置、基顶点位置、起始实例位置。

仅当网格中的子网格具有不同拓扑(例如,三角形和线条)时,Unity 才需要 submeshIndex 参数。否则,有关要绘制哪个子网格的所有信息都将来自 bufferWithArgs 参数。

下面是一个可用于绘制同一网格的多个实例的脚本:

using UnityEngine;
using System.Collections;

public class ExampleClass : MonoBehaviour { public int instanceCount = 100000; public Mesh instanceMesh; public Material instanceMaterial; public int subMeshIndex = 0;

private int cachedInstanceCount = -1; private int cachedSubMeshIndex = -1; private ComputeBuffer positionBuffer; private ComputeBuffer argsBuffer; private uint[] args = new uint[5] { 0, 0, 0, 0, 0 };

void Start() { argsBuffer = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments); UpdateBuffers(); }

void Update() { // Update starting position buffer if (cachedInstanceCount != instanceCount || cachedSubMeshIndex != subMeshIndex) UpdateBuffers();

// Pad input if (Input.GetAxisRaw("Horizontal") != 0.0f) instanceCount = (int)Mathf.Clamp(instanceCount + Input.GetAxis("Horizontal") * 40000, 1.0f, 5000000.0f);

// Render Graphics.DrawMeshInstancedIndirect(instanceMesh, subMeshIndex, instanceMaterial, new Bounds(Vector3.zero, new Vector3(100.0f, 100.0f, 100.0f)), argsBuffer); }

void OnGUI() { GUI.Label(new Rect(265, 25, 200, 30), "Instance Count: " + instanceCount.ToString()); instanceCount = (int)GUI.HorizontalSlider(new Rect(25, 20, 200, 30), (float)instanceCount, 1.0f, 5000000.0f); }

void UpdateBuffers() { // Ensure submesh index is in range if (instanceMesh != null) subMeshIndex = Mathf.Clamp(subMeshIndex, 0, instanceMesh.subMeshCount - 1);

// Positions if (positionBuffer != null) positionBuffer.Release(); positionBuffer = new ComputeBuffer(instanceCount, 16); Vector4[] positions = new Vector4[instanceCount]; for (int i = 0; i < instanceCount; i++) { float angle = Random.Range(0.0f, Mathf.PI * 2.0f); float distance = Random.Range(20.0f, 100.0f); float height = Random.Range(-2.0f, 2.0f); float size = Random.Range(0.05f, 0.25f); positions[i] = new Vector4(Mathf.Sin(angle) * distance, height, Mathf.Cos(angle) * distance, size); } positionBuffer.SetData(positions); instanceMaterial.SetBuffer("positionBuffer", positionBuffer);

// Indirect args if (instanceMesh != null) { args[0] = (uint)instanceMesh.GetIndexCount(subMeshIndex); args[1] = (uint)instanceCount; args[2] = (uint)instanceMesh.GetIndexStart(subMeshIndex); args[3] = (uint)instanceMesh.GetBaseVertex(subMeshIndex); } else { args[0] = args[1] = args[2] = args[3] = 0; } argsBuffer.SetData(args);

cachedInstanceCount = instanceCount; cachedSubMeshIndex = subMeshIndex; }

void OnDisable() { if (positionBuffer != null) positionBuffer.Release(); positionBuffer = null;

if (argsBuffer != null) argsBuffer.Release(); argsBuffer = null; } }

下面是一个表面着色器,可以与上面的示例脚本配合使用:

          Shader "Instanced/InstancedSurfaceShader" {
    Properties {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200

CGPROGRAM // Physically based Standard lighting model #pragma surface surf Standard addshadow fullforwardshadows #pragma multi_compile_instancing #pragma instancing_options procedural:setup

sampler2D _MainTex;

struct Input { float2 uv_MainTex; };

#ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED StructuredBuffer<float4> positionBuffer; #endif

void rotate2D(inout float2 v, float r) { float s, c; sincos(r, s, c); v = float2(v.x * c - v.y * s, v.x * s + v.y * c); }

void setup() { #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED float4 data = positionBuffer[unity_InstanceID];

float rotation = data.w * data.w * _Time.y * 0.5f; rotate2D(data.xz, rotation);

unity_ObjectToWorld._11_21_31_41 = float4(data.w, 0, 0, 0); unity_ObjectToWorld._12_22_32_42 = float4(0, data.w, 0, 0); unity_ObjectToWorld._13_23_33_43 = float4(0, 0, data.w, 0); unity_ObjectToWorld._14_24_34_44 = float4(data.xyz, 1); unity_WorldToObject = unity_ObjectToWorld; unity_WorldToObject._14_24_34 *= -1; unity_WorldToObject._11_22_33 = 1.0f / unity_WorldToObject._11_22_33; #endif }

half _Glossiness; half _Metallic;

void surf (Input IN, inout SurfaceOutputStandard o) { fixed4 c = tex2D (_MainTex, IN.uv_MainTex); o.Albedo = c.rgb; o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = c.a; } ENDCG } FallBack "Diffuse" }

下面是一个自定义着色器,可以与上面的示例脚本配合使用:

          Shader "Instanced/InstancedShader" {
    Properties {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
    }
    SubShader {

Pass {

Tags {"LightMode"="ForwardBase"}

CGPROGRAM

#pragma vertex vert #pragma fragment frag #pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlight #pragma target 4.5

#include "UnityCG.cginc" #include "UnityLightingCommon.cginc" #include "AutoLight.cginc"

sampler2D _MainTex;

#if SHADER_TARGET >= 45 StructuredBuffer<float4> positionBuffer; #endif

struct v2f { float4 pos : SV_POSITION; float2 uv_MainTex : TEXCOORD0; float3 ambient : TEXCOORD1; float3 diffuse : TEXCOORD2; float3 color : TEXCOORD3; SHADOW_COORDS(4) };

void rotate2D(inout float2 v, float r) { float s, c; sincos(r, s, c); v = float2(v.x * c - v.y * s, v.x * s + v.y * c); }

v2f vert (appdata_full v, uint instanceID : SV_InstanceID) { #if SHADER_TARGET >= 45 float4 data = positionBuffer[instanceID]; #else float4 data = 0; #endif

float rotation = data.w * data.w * _Time.x * 0.5f; rotate2D(data.xz, rotation);

float3 localPosition = v.vertex.xyz * data.w; float3 worldPosition = data.xyz + localPosition; float3 worldNormal = v.normal;



float3 ndotl = saturate(dot(worldNormal, _WorldSpaceLightPos0.xyz)); float3 ambient = ShadeSH9(float4(worldNormal, 1.0f)); float3 diffuse = (ndotl * _LightColor0.rgb); float3 color = v.color;

v2f o; o.pos = mul(UNITY_MATRIX_VP, float4(worldPosition, 1.0f)); o.uv_MainTex = v.texcoord; o.ambient = ambient; o.diffuse = diffuse; o.color = color; TRANSFER_SHADOW(o) return o; }

fixed4 frag (v2f i) : SV_Target { fixed shadow = SHADOW_ATTENUATION(i); fixed4 albedo = tex2D(_MainTex, i.uv_MainTex); float3 lighting = i.diffuse * shadow + i.ambient; fixed4 output = fixed4(albedo.rgb * i.color * lighting, albedo.w); UNITY_APPLY_FOG(i.fogCoord, output); return output; }

ENDCG } } }