{"id":249,"date":"2016-04-22T22:37:21","date_gmt":"2016-04-22T22:37:21","guid":{"rendered":"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/?p=249"},"modified":"2016-04-25T21:26:28","modified_gmt":"2016-04-25T21:26:28","slug":"graphics-optimization-1-lighting","status":"publish","type":"post","link":"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/index.php\/2016\/04\/22\/graphics-optimization-1-lighting\/","title":{"rendered":"Graphics &amp; Optimization #1: Lighting"},"content":{"rendered":"<p>It is always necessary to squeeze out every drop of performance in order to run a 3D app on an Android phone. Running the app in VR even doubles the necessity and difficulty.\u00a0Fortunately, for VR Rehearsal, the fact that the position of presenter and virtual audiences in the environment remains still makes it possible to remarkably optimize the graphics performance in several aspects. We managed to render more than 50 animated humanoid models in a realistic environment with decent framerate by applying a number of tricks.<\/p>\n<p>Because our environments are completely static, lightmap is the nice solution which pre-computes the lighting data in a texture even with GI information. Then in real-time, the shader can get the lighting by simply querying the lightmap texture. The following code is the diffuse shader used for rendering the environment that reads the lightmap data to apply lighting:<\/p>\n<p>&nbsp;<\/p>\n<p>Shader &#8220;VR_Rehearsal_app\/LightmapDiffuse&#8221;<br \/>\n{<br \/>\nProperties<br \/>\n{<br \/>\n_MainTex (&#8220;Texture&#8221;, 2D) = &#8220;white&#8221; {}<br \/>\n}<br \/>\nSubShader<br \/>\n{<br \/>\nTags<br \/>\n{<br \/>\n&#8220;RenderType&#8221;=&#8221;Opaque&#8221;<br \/>\n&#8220;Queue&#8221; = &#8220;Geometry&#8221;<br \/>\n&#8220;IgnoreProjector&#8221; = &#8220;True&#8221;<\/p>\n<p>}<br \/>\nLOD 100<\/p>\n<p>Pass<br \/>\n{<br \/>\nName &#8220;Lightmap&#8221;<br \/>\nLighting Off<\/p>\n<p>CGPROGRAM<br \/>\n#pragma vertex vert<br \/>\n#pragma fragment frag<br \/>\n#pragma fragmentoption ARB_precision_hint_fastest<br \/>\n\/\/ make fog work<br \/>\n\/\/#pragma multi_compile_fog<\/p>\n<p>#include &#8220;UnityCG.cginc&#8221;<\/p>\n<p>struct appdata<br \/>\n{<br \/>\nhalf4 vertex : POSITION;<br \/>\nhalf2 uv : TEXCOORD0;<br \/>\nhalf2 uv_lm : TEXCOORD1;<br \/>\n};<\/p>\n<p>struct v2f<br \/>\n{<br \/>\nhalf4 vertex : SV_POSITION;<br \/>\nhalf4 uv : TEXCOORD0; \/\/xy: texture uv; zw: lightmap uv<br \/>\n\/\/UNITY_FOG_COORDS(1)<br \/>\n};<\/p>\n<p>uniform sampler2D _MainTex;<br \/>\nuniform half4 _MainTex_ST;<\/p>\n<p>v2f vert (appdata v)<br \/>\n{<br \/>\nv2f o;<br \/>\nUNITY_INITIALIZE_OUTPUT(v2f, o);<br \/>\no.vertex = mul(UNITY_MATRIX_MVP, v.vertex);<br \/>\no.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);<br \/>\no.uv.zw = v.uv_lm *unity_LightmapST.xy + unity_LightmapST.zw;<br \/>\n\/\/UNITY_TRANSFER_FOG(o,o.vertex);<br \/>\nreturn o;<br \/>\n}<\/p>\n<p>fixed4 frag (v2f i) : SV_Target<br \/>\n{<br \/>\n\/\/ sample the texture<br \/>\nfixed4 col = tex2D(_MainTex, i.uv);<br \/>\n\/\/ apply fog<br \/>\ncol.rgb *= DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, i.uv.zw));<br \/>\n\/\/UNITY_APPLY_FOG(i.fogCoord, col);<br \/>\nreturn col;<br \/>\n}<br \/>\nENDCG<br \/>\n}<br \/>\n}<br \/>\n}<\/p>\n<p>&nbsp;<\/p>\n<p>The fragment shader here is extremely simple: one line for reading diffuse texture, another line for reading lightmap, and multiple them together (We can apply the fog as well without too much pain). However, the resulting quality is surprisingly good:<\/p>\n<div id=\"attachment_267\" style=\"width: 910px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-267\" class=\"wp-image-267 size-large\" src=\"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/wp-content\/uploads\/2016\/04\/rpis-1024x523.png\" alt=\"rpis\" width=\"900\" height=\"460\" srcset=\"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/wp-content\/uploads\/2016\/04\/rpis-1024x523.png 1024w, https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/wp-content\/uploads\/2016\/04\/rpis-300x153.png 300w, https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/wp-content\/uploads\/2016\/04\/rpis-768x392.png 768w\" sizes=\"(max-width: 900px) 100vw, 900px\" \/><p id=\"caption-attachment-267\" class=\"wp-caption-text\">Large auditorium environment<\/p><\/div>\n<div id=\"attachment_268\" style=\"width: 910px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-268\" class=\"wp-image-268 size-large\" src=\"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/wp-content\/uploads\/2016\/04\/conf-1024x523.png\" alt=\"conf\" width=\"900\" height=\"460\" srcset=\"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/wp-content\/uploads\/2016\/04\/conf-1024x523.png 1024w, https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/wp-content\/uploads\/2016\/04\/conf-300x153.png 300w, https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/wp-content\/uploads\/2016\/04\/conf-768x392.png 768w\" sizes=\"(max-width: 900px) 100vw, 900px\" \/><p id=\"caption-attachment-268\" class=\"wp-caption-text\">Small conference room environment<\/p><\/div>\n<p>However the reality isn\u2019t always so easy. It took us long time to tweak the parameters of Unity\u2019s lightmap system in order to generate good lightmaps for our scenes. At first we were troubled by the problem that some triangles receives light bleeding in from the lightmap for another triangle. This was solved by closing all the edges of the model, and increasing the margin between lightmap patches. Then we experienced strange stripe artifacts on the walls of the conference room environment. It turned out that the problem was from inappropriate shadow parameters, and was solved by increasing the baked shadow radius of all the scene lights.<\/p>\n<p>&nbsp;<\/p>\n<p>We\u2019ve removed real-time light calculation for static environment, but what about characters with animations that are instantiated in runtime? It is impossible to pre-compute lightmap for moving objects in real time. But fortunately, Unity also provides light probe which is based on the idea that the lighting at any position can then be approximated by interpolating between discrete samples. The interpolation is far cheaper than real lighting, yet still produces a reasonably good quality. All we need to do is to setup our sampling points in the scene:<\/p>\n<div id=\"attachment_273\" style=\"width: 910px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-273\" class=\"wp-image-273 size-large\" src=\"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/wp-content\/uploads\/2016\/04\/lp_conf-1024x523.png\" alt=\"lp_conf\" width=\"900\" height=\"460\" srcset=\"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/wp-content\/uploads\/2016\/04\/lp_conf-1024x523.png 1024w, https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/wp-content\/uploads\/2016\/04\/lp_conf-300x153.png 300w, https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/wp-content\/uploads\/2016\/04\/lp_conf-768x392.png 768w\" sizes=\"(max-width: 900px) 100vw, 900px\" \/><p id=\"caption-attachment-273\" class=\"wp-caption-text\">Setting up light probes in conference room<\/p><\/div>\n<div id=\"attachment_271\" style=\"width: 910px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-271\" class=\"wp-image-271 size-large\" src=\"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/wp-content\/uploads\/2016\/04\/lp_rpis-1024x523.png\" alt=\"lp_rpis\" width=\"900\" height=\"460\" srcset=\"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/wp-content\/uploads\/2016\/04\/lp_rpis-1024x523.png 1024w, https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/wp-content\/uploads\/2016\/04\/lp_rpis-300x153.png 300w, https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/wp-content\/uploads\/2016\/04\/lp_rpis-768x392.png 768w\" sizes=\"(max-width: 900px) 100vw, 900px\" \/><p id=\"caption-attachment-271\" class=\"wp-caption-text\">Setting up light probes in auditorium<\/p><\/div>\n<p>and use those information to interpolate lighting in our shader. The interpolation can be either done in vertex shader or fragment shader. Obviously, we choose to do that in vertex shader:<\/p>\n<p><code><br \/>\nShader \"VR_Rehearsal_app\/LightProbeDiffuse\"<br \/>\n{<br \/>\nProperties<br \/>\n{<br \/>\n_MainTex (\"Albedo (RGB)\", 2D) = \"white\" {}<br \/>\n}<br \/>\nSubShader<br \/>\n{<br \/>\nTags<br \/>\n{<br \/>\n\"RenderType\"=\"Opaque\"<br \/>\n\"Queue\" = \"Geometry\"<br \/>\n\"IgnoreProjector\" = \"True\"<br \/>\n\"BW\"=\"TrueProbes\"<br \/>\n}<br \/>\nLOD 100<\/code><\/p>\n<p>Pass<br \/>\n{<br \/>\nName &#8220;LightProbe&#8221;<br \/>\nTags<br \/>\n{<br \/>\n&#8220;LightMode&#8221; = &#8220;ForwardBase&#8221;<br \/>\n}<br \/>\n\/\/Fog<br \/>\n\/\/{ Mode Global }<br \/>\nCGPROGRAM<\/p>\n<p>#pragma vertex vert<br \/>\n#pragma fragment frag<br \/>\n#pragma fragmentoption ARB_precision_hint_fastest<br \/>\n#include &#8220;UnityCG.cginc&#8221;<br \/>\n#include &#8220;Lighting.cginc&#8221;<\/p>\n<p>struct appdata<br \/>\n{<br \/>\nhalf4 vertex : POSITION;<br \/>\nfixed3 normal : NORMAL;<br \/>\nhalf2 texcoord : TEXCOORD0;<br \/>\n};<\/p>\n<p>struct v2f<br \/>\n{<br \/>\nhalf4 pos : SV_POSITION;<br \/>\nhalf2 uv : TEXCOORD0;<br \/>\n\/\/fixed4 vLightingFog : TEXCOORD1; \/\/xyz: vertex light color; (w: vertex fog data)<br \/>\nfixed3 vLightingFog : TEXCOORD1; \/\/xyz: vertex light color; (w: vertex fog data)<br \/>\n};<\/p>\n<p>uniform sampler2D _MainTex;<br \/>\nuniform half4 _MainTex_ST;<\/p>\n<p>v2f vert(appdata v)<br \/>\n{<br \/>\nv2f output;<br \/>\nUNITY_INITIALIZE_OUTPUT(v2f, output);<\/p>\n<p>output.pos = mul(UNITY_MATRIX_MVP, v.vertex);<br \/>\noutput.uv = TRANSFORM_TEX(v.texcoord, _MainTex);<\/p>\n<p>half3 worldPos = mul((float3x3)_Object2World, v.vertex.xyz);<br \/>\nfixed3 worldN = mul((float3x3)_Object2World, v.normal);<\/p>\n<p>\/\/light probe<br \/>\noutput.vLightingFog.xyz = ShadeSH9 (float4(worldN,1.0));<\/p>\n<p>\/\/output.vLightingFog.w = exp(-length(_WorldSpaceCameraPos &#8211; worldPos) * unity_FogParams.x);<br \/>\nreturn output;<br \/>\n}<\/p>\n<p>fixed4 frag(v2f input) : SV_TARGET<br \/>\n{<br \/>\nfixed4 col = tex2D(_MainTex, input.uv) * fixed4(input.vLightingFog.xyz, 1.0);<br \/>\nreturn col;<br \/>\n\/\/return lerp(unity_FogColor, col, input.vLightingFog.w);<br \/>\n}<br \/>\nENDCG<\/p>\n<p>}<\/p>\n<p>}<\/p>\n<p>FallBack Off<br \/>\n\/\/FallBack &#8220;Mobile\/VertexLit&#8221;<br \/>\n}<\/p>\n<p>&nbsp;<\/p>\n<p>Notice the function ShadeSH9 in vertex shader is the interpolation function. And the fragment shader is still simple: read the diffuse texture and multiple the color by per-vertex interpolated lighting. The following pictures show the shaded character using light probe and normal diffuse shader.<\/p>\n<div id=\"attachment_282\" style=\"width: 910px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-282\" class=\"size-large wp-image-282\" src=\"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/wp-content\/uploads\/2016\/04\/shader_comp-1024x523.png\" alt=\"Left: Light probed diffuse shader. Right: Unity standard shader.\" width=\"900\" height=\"460\" srcset=\"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/wp-content\/uploads\/2016\/04\/shader_comp-1024x523.png 1024w, https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/wp-content\/uploads\/2016\/04\/shader_comp-300x153.png 300w, https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/wp-content\/uploads\/2016\/04\/shader_comp-768x392.png 768w\" sizes=\"(max-width: 900px) 100vw, 900px\" \/><p id=\"caption-attachment-282\" class=\"wp-caption-text\">Left: Light probed diffuse shader. Right: Unity standard shader.<\/p><\/div>\n<p>Now we have precomputed lighting data (either lightmap or lightprobe) for different environments, the last thing to do is to load them correctly. We need to deal with dynamically loading those data because we generate anything in the same scene (which is better to manage than multiple scenes). However, it turns out that unity by default cannot bake lightmaps into prefabs, and it cannot automatically load multiple sets of lightprobe. We end up writing our own helper scripts to bake lightmaps into prefabs based on this thread: <a href=\"http:\/\/forum.unity3d.com\/threads\/problems-with-instantiating-baked-prefabs.324514\/\" target=\"_blank\">http:\/\/forum.unity3d.com\/threads\/problems-with-instantiating-baked-prefabs.324514\/<\/a>, and exporting all the lightprobe information as binary files.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>It is always necessary to squeeze out every drop of performance in order to run a 3D app on an Android phone. Running the app in VR even doubles the necessity and difficulty.\u00a0Fortunately, for VR Rehearsal, the fact that the position of presenter and virtual audiences in the environment remains still makes it possible to <a href=\"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/index.php\/2016\/04\/22\/graphics-optimization-1-lighting\/\" rel=\"nofollow\"><span class=\"sr-only\">Read more about Graphics &amp; Optimization #1: Lighting<\/span>[&hellip;]<\/a><\/p>\n","protected":false},"author":6,"featured_media":273,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"pgc_meta":"","footnotes":""},"categories":[1],"tags":[],"class_list":["post-249","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-vr-rehearsal"],"_links":{"self":[{"href":"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/index.php\/wp-json\/wp\/v2\/posts\/249"}],"collection":[{"href":"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/index.php\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/index.php\/wp-json\/wp\/v2\/comments?post=249"}],"version-history":[{"count":20,"href":"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/index.php\/wp-json\/wp\/v2\/posts\/249\/revisions"}],"predecessor-version":[{"id":297,"href":"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/index.php\/wp-json\/wp\/v2\/posts\/249\/revisions\/297"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/index.php\/wp-json\/wp\/v2\/media\/273"}],"wp:attachment":[{"href":"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/index.php\/wp-json\/wp\/v2\/media?parent=249"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/index.php\/wp-json\/wp\/v2\/categories?post=249"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/projects-old.etc.cmu.edu\/vr-rehearsal\/index.php\/wp-json\/wp\/v2\/tags?post=249"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}