Unity3D学习笔记7——GPU实例化(2)
目录
1. 概述
在上一篇文章《Unity3D学习笔记6——GPU实例化(1)》详细介绍了Unity3d中GPU实例化的实现,并且给出了详细代码。不过其着色器实现是简单的顶点+片元着色器实现的。Unity提供的很多着色器是表面着色器,通过表面着色器,也是可以实现GPU实例化的。
2. 详论
2.1. 实现
首先,我们还是挂接与上篇文章一样的脚本:
using UnityEngine; [ExecuteInEditMode] public class Note7Main : MonoBehaviour { public Mesh mesh; public Material material; int instanceCount = 200; Bounds instanceBounds; ComputeBuffer bufferWithArgs = null; ComputeBuffer instanceParamBufferData = null; // Start is called before the first frame update void Start() { instanceBounds = new Bounds(new Vector3(0, 0, 0), new Vector3(100, 100, 100)); uint[] args = new uint[5] { 0, 0, 0, 0, 0 }; bufferWithArgs = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments); int subMeshIndex = 0; args[0] = mesh.GetIndexCount(subMeshIndex); args[1] = (uint)instanceCount; args[2] = mesh.GetIndexStart(subMeshIndex); args[3] = mesh.GetBaseVertex(subMeshIndex); bufferWithArgs.SetData(args); InstanceParam[] instanceParam = new InstanceParam[instanceCount]; for (int i = 0; i < instanceCount; i++) { Vector3 position = Random.insideUnitSphere * 5; Quaternion q = Quaternion.Euler(Random.Range(0.0f, 90.0f), Random.Range(0.0f, 90.0f), Random.Range(0.0f, 90.0f)); float s = Random.value; Vector3 scale = new Vector3(s, s, s); instanceParam[i].instanceToObjectMatrix = Matrix4x4.TRS(position, q, scale); instanceParam[i].color = Random.ColorHSV(); } int stride = System.Runtime.InteropServices.Marshal.SizeOf(typeof(InstanceParam)); instanceParamBufferData = new ComputeBuffer(instanceCount, stride); instanceParamBufferData.SetData(instanceParam); material.SetBuffer("dataBuffer", instanceParamBufferData); material.SetMatrix("ObjectToWorld", Matrix4x4.identity); } // Update is called once per frame void Update() { if (bufferWithArgs != null) { Graphics.DrawMeshInstancedIndirect(mesh, 0, material, instanceBounds, bufferWithArgs, 0); } } private void OnDestroy() { if (bufferWithArgs != null) { bufferWithArgs.Release(); } if (instanceParamBufferData != null) { instanceParamBufferData.Release(); } } }
不过,脚本的材质设置需要使用我们新的材质:
这个材质可以通过使用Standard Surface Shader作为我们修改的模板:
修改后的着色器代码如下:
Shader "Custom/SimpleSurfaceIntanceShader" { Properties { _Color ("Color", Color) = (1,1,1,1) _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, and enable shadows on all light types #pragma surface surf Standard fullforwardshadows #pragma target 4.5 #pragma multi_compile_instancing #pragma instancing_options procedural:setup struct InstanceParam { float4 color; float4x4 instanceToObjectMatrix; }; #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED StructuredBuffer<InstanceParam> dataBuffer; #endif float4x4 ObjectToWorld; sampler2D _MainTex; struct Input { float2 uv_MainTex; }; half _Glossiness; half _Metallic; fixed4 _Color; void setup() { #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED InstanceParam data = dataBuffer[unity_InstanceID]; unity_ObjectToWorld = mul(ObjectToWorld, data.instanceToObjectMatrix); #endif } void surf (Input IN, inout SurfaceOutputStandard o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; // Metallic and smoothness come from slider variables o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = c.a; } ENDCG } FallBack "Diffuse" }
最后的显示效果如下:
2.2. 解析
对比修改之前的着色器代码:
#pragma multi_compile_instancing
的意思是给这个着色器增加了实例化的变体,也就是增加了诸如INSTANCING_ON PROCEDURAL_ON这样的关键字,可以编译实例化的着色器版本。#pragma instancing_options procedural:setup
是搭配Graphics.DrawMeshInstancedIndirect
使用的,在顶点着色器阶段开始时,Unity会调用冒号后指定的setup()函数。- setup()函数的意思是通过实例化Id也就是unity_InstanceID,找到正确的实例化数据,并且调整Unity的内置变量unity_ObjectToWorld——也就是模型矩阵。正如上一篇文章所言,GPU实例化的关键就在于模型矩阵的重新计算。在Unity API官方示例中,还修改了其逆矩阵unity_WorldToObject。
3. 参考
- 《Unity3D学习笔记6——GPU实例化(1)》
- Graphics.DrawMeshInstancedIndirect
- Declaring and using shader keywords in HLSL
- Creating shaders that support GPU instancing
分类: Unity3D