【Unity】Compute Shader计算BRDF存储到纹理
在移动平台,BRDF的计算会消耗较多的ALU,同时对于BRDF计算结果的精度要求没有这么高。一种优化的方式是将BRDF的部分复杂计算,预计算存储到纹理中。已增加一张BRDF贴图和纹理采样器的代价,来节约ALU。
将BRDF预计算存储到纹理的方式有很多,使用C#实现或者Unity2018的新的Custom Render Texture都可以很方便的实现功能。
作为一个Compute Shader的入门,这里使用Compute Shader,也就是计算着色器来实现我们的需求。
首先,我们新建一个C#脚本,BRDFGenerator.cs
1 using UnityEditor; 2 using UnityEngine; 3 using System.Collections; 4 using System.Collections.Generic; 5 public class BRDFGenerator : ScriptableWizard 6 { 7 public ComputeShader computeShader; 8 public int size=256; 9 // public RenderTexture rt; 10 public Material mat; 11 private int _kernel; 12 [MenuItem("Texture Generator/BRDF Texture Generator")] 13 static void CreateWizard() 14 { 15 // ScriptableWizard.DisplayWizard<t>("Create Light", "Create", "Apply"); 16 ScriptableWizard.DisplayWizard<BRDFGenerator>("BRDF Texture Generator", "生成"); 17 //If you don't want to use the secondary button simply leave it out: 18 } 19 private void OnEnable() 20 { 21 Debug.Log("Start!"); 22 } 23 void OnWizardCreate() 24 { 25 RenderTexture renderTexture = new RenderTexture(size,size,0,RenderTextureFormat.ARGB32); 26 renderTexture.enableRandomWrite = true; 27 renderTexture.Create(); 28 // rt.enableRandomWrite = true; 29 mat.SetTexture("_MainTex", renderTexture); 30 float temp = size / 1.0f; 31 _kernel = computeShader.FindKernel("CSMain"); 32 computeShader.SetTexture(_kernel, "Result", renderTexture); 33 computeShader.SetFloat("size", temp); 34 computeShader.Dispatch(_kernel, renderTexture.width / 8, renderTexture.height / 8, 1); 35 Debug.Log("Sucess!"); 36 } 37 38 void OnWizardUpdate() 39 { 40 helpString = "BRDF"; 41 } 42 43 }
BRDFGenerator.cs实现了一个窗口
同时,还进行了线程的分发,这里,我们一个线程控制一个像素点。
对应的Compute Shader如下:
1 // Each #kernel tells which function to compile; you can have many kernels 2 #pragma kernel CSMain 3 4 // Create a RenderTexture with enableRandomWrite flag and set it 5 // with cs.SetTexture 6 RWTexture2D<float4> Result; 7 float size; 8 #define UNITY_PI 3.14159265359f 9 #define UNITY_INV_PI 0.31830988618f 10 float Pow5(float x) 11 { 12 return x*x*x*x*x; 13 } 14 [numthreads(8,8,1)] 15 void CSMain (uint3 id : SV_DispatchThreadID) 16 { 17 // TODO: insert actual code here! 18 float x=id.x/size; 19 float y=id.y/size; 20 //可见性项计算 21 float roughness=x; 22 float r2=roughness*roughness; 23 float dot2=y*y; 24 float V= (-1 + sqrt(r2 * (1 - dot2) / dot2 + 1)) * 0.5f; 25 //微表面分布项计算 26 float nh=y; 27 float d_temp = (nh * r2 - nh) * nh + 1.0f; 28 float D= UNITY_INV_PI * r2 / (d_temp * d_temp + 1e-7f); 29 //菲涅尔计算 30 float cosA=x; 31 float F=Pow5(1-cosA); 32 //漫反射计算 33 float fd90=y*2.5; 34 float diffuse = (1 + (fd90 - 1) * Pow5(1 - x))*0.4; 35 //输出结果 36 Result[id.xy] = float4(V, D, F, diffuse); 37 }
Compute Shader的语法和顶点及片段着色器没有什么区别,SV_DispatchThreadID的具体解释可以查阅MSDN。
这里使用的BRDF的计算都是摘取的UnityStandardBRDF.cginc的实现,也可以选择自己的BRDF模型。
BRDF的各项分别存储在RGBA四个通道中。计算速度比在C#中快超多。
结果如下:
RGB通道
Alpha通道