- 用户指南
- 资源商店 (Asset Store)
- 资源服务器 (Asset Server)(仅限团队许可证)
- 缓存服务器(仅限团队许可证)
- 幕后场景
- 创建游戏
- 运行时实例化预设 (Prefabs)
- 变换 (Transforms)
- 物理
- 添加随机的游戏元素
- 粒子系统(Particle Systems)
- Mecanim 动画系统
- 旧动画系统
- 导航网格 (Navmesh) 和寻路 (Pathfinding)(仅限专业版 (Pro))
- Sound (音频侦听器)
- 游戏界面元素
- 多玩家联网游戏
- iOS 开发入门
- Android 开发入门
- Blackberry 10 开发入门
- Metro:入门指南
- 本地客户端开发入门
- FAQ
- Advanced
- Vector Cookbook
- 资源包(仅限专业版)
- Graphics Features
- 资源数据库 (AssetDatabase)
- 构建播放器管道
- 分析器(仅限专业版)
- 光照贴图快速入门
- 遮挡剔除(仅限专业版)
- 相机使用技巧
- 运行时加载资源
- 通过脚本修改源资源
- 用程序生成网格几何体
- 富文本
- 在 Unity 工程 (Project) 中使用 Mono DLL
- 事件函数的执行顺序
- 移动优化实用指南
- Unity XCode 工程结构
- 优化图形性能
- 减少文件大小
- 理解自动内存管理
- 平台依赖编译
- 泛型函数
- 调试
- 插件(专业版/移动版特有功能)
- 文本场景文件格式(仅限专业版)
- 流媒体资源
- 启动时运行编辑器脚本代码
- 网络模拟
- VisualStudio C 集成
- 分析
- 检查更新
- 安装多版本 Unity
- 故障排除
- Unity 中的阴影
- Unity 中的 IME
- 对集成显卡进行优化
- 网络播放器 (Web Player) 部署
- 使用网络播放器中的信任链系统
移动优化实用指南 — 渲染优化
本节将介绍渲染优化的技术细节。它展示了如何烘焙光照结果以达到更佳性能,以及 Shadowgun 的开发人员如何平衡高对比度纹理以及光照烘焙,让游戏更加精美。如果您想了解有关经过优化的移动游戏的一般信息,请参阅图形方法页面。
秀出艺术才华!
有时候,优化游戏的渲染是一种粗活累活。Unity 提供 的所有结构都可以轻松加快游戏的运行速度,但如果要求在有限的设备上实现出类拔萃的保真度,那么,独立完成并且回避这些结构才是正确的选择,只要可以引进让游戏运行更快速的关键结构变化。可以选择的工具包括编辑器脚本、简单着色器,以及良好的旧式艺术作品。
Unity Indie 用户请注意:此处所指的编辑器脚本使用渲染纹理 (RenderTextures) 让产品更加光滑,因此,它们不会马上有所帮助,但其背后的原则对屏幕截图同样有效,因此,您可以任意使用这些技巧烘焙属于自己的纹理。
如何进行深入了解?
首先,请参阅着色器编写简介。
- 内置着色器
- 查看内置着色器的源代码。通常,如果需要制作一款与众不同的新着色器,可以借鉴两个已经存在的着色器,并将其合并在一起。
- 表面着色器调试(#pragma 调试)
- CG 着色器由每个表面着色器生成,然后在那里彻底编译。如果将 #pragma debug 添加至表面着色器顶部,在通过检视器打开编译的着色器时,将看到 CG 中间代码。这有助于检视着色器的特定部分在实际上如何计算,并且它也有助于从表面着色器中提取您想要的某些方面,并将其应用到 CG 着色器。
- 着色器包含文件
- 大部分着色器辅助代码都包含在每个着色器中,并且通常不会使用,但这也正是您有时候可以看到着色器调用 WorldReflectionVector 之类的函数,但这些函数似乎没有在任何地方定义的原因。Unity 拥有若干内置着色器包含文件,这些文件就包括这些辅助定义。如需查找特定参数,必须在所有不同的包含文件中搜索。
- 这些文件是 Unity 用来简化着色器编写的内部结构的主要组成部分;它们可以提供 实时阴影、不同的光照类型、光照贴图以及多平台支持等功能。
- 硬件文档
请注意,对于浮点精度提示,建议您更加大胆尝试。
Shadowgun 深入分析
考虑到运行的硬件,Shadowgun 无愧为一项突出的图形成就。其艺术品质是解答这一秘密的关键,当然还有一些其他技巧,让程序员可以实现这一品质,并将设计师的潜力发挥到极限。
在图形方法页面,我们将 Shadowgun 中的黄金雕像作为其非凡优化的一个示例,他们没有使用正常的贴图来获得可靠的清晰度,相反,他们只是将光照细节烘焙成纹理。在这里,我们将向您展示在您自己的游戏中使用类似技术的方式和原因。
function showhide(link) { var theDiv = document.getElementById(link.id+"_div"); if (theDiv) { var curState = theDiv.style.display != 'none'; theDiv.style.display = (curState ? 'none' : 'block'); var str = (curState ? "+ Show [" : "- Hide [") + link.title + (curState ? "] +" : "] -"); link.innerHTML = str; } } + Show [Shader code for Real-Time vs Baked Golden Statue] +// This is the pixel shader code for drawing normal-mapped // specular highlights on static lightmapped geometry // 5 texture reads, lots of instructions SurfaceOutput o; fixed4 tex = tex2D(_MainTex, IN.uv_MainTex); fixed4 c = tex * _Color; o.Albedo = c.rgb; o.Gloss = tex.a; o.Specular = _Shininess; o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap)); float3 worldRefl = WorldReflectionVector (IN, o.Normal); fixed4 reflcol = texCUBE (_Cube, worldRefl); reflcol *= tex.a; o.Emission = reflcol.rgb * _ReflectColor.rgb; o.Alpha = reflcol.a * _ReflectColor.a; fixed atten = LIGHT_ATTENUATION(IN); fixed4 c = 0; half3 specColor; fixed4 lmtex = tex2D(unity_Lightmap, IN.lmap.xy); fixed4 lmIndTex = tex2D(unity_LightmapInd, IN.lmap.xy); const float3x3 unity_DirBasis = float3x3( float3( 0.81649658, 0.0, 0.57735028), float3(-0.40824830, 0.70710679, 0.57735027), float3(-0.40824829, -0.70710678, 0.57735026) ); half3 lm = DecodeLightmap (lmtex); half3 scalePerBasisVector = DecodeLightmap (lmIndTex); half3 normalInRnmBasis = saturate (mul (unity_DirBasis, o.Normal)); lm *= dot (normalInRnmBasis, scalePerBasisVector); return half4(lm, 1); | // This is the pixel shader code for lighting which is // baked into the texture // 2 texture reads, very few instructions fixed4 c = tex2D (_MainTex, i.uv.xy); c.xyz += texCUBE(_EnvTex,i.refl) * _ReflectionColor * c.a; return c; |
// This is the pixel shader code for drawing normal-mapped // specular highlights on static lightmapped geometry // 5 texture reads, lots of instructions SurfaceOutput o; fixed4 tex = tex2D(_MainTex, IN.uv_MainTex); fixed4 c = tex * _Color; o.Albedo = c.rgb; o.Gloss = tex.a; o.Specular = _Shininess; o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap)); float3 worldRefl = WorldReflectionVector (IN, o.Normal); fixed4 reflcol = texCUBE (_Cube, worldRefl); reflcol *= tex.a; o.Emission = reflcol.rgb * _ReflectColor.rgb; o.Alpha = reflcol.a * _ReflectColor.a; fixed atten = LIGHT_ATTENUATION(IN); fixed4 c = 0; half3 specColor; fixed4 lmtex = tex2D(unity_Lightmap, IN.lmap.xy); fixed4 lmIndTex = tex2D(unity_LightmapInd, IN.lmap.xy); const float3x3 unity_DirBasis = float3x3( float3( 0.81649658, 0.0, 0.57735028), float3(-0.40824830, 0.70710679, 0.57735027), float3(-0.40824829, -0.70710678, 0.57735026) ); half3 lm = DecodeLightmap (lmtex); half3 scalePerBasisVector = DecodeLightmap (lmIndTex); half3 normalInRnmBasis = saturate (mul (unity_DirBasis, o.Normal)); lm *= dot (normalInRnmBasis, scalePerBasisVector); return half4(lm, 1); | // This is the pixel shader code for lighting which is // baked into the texture // 2 texture reads, very few instructions fixed4 c = tex2D (_MainTex, i.uv.xy); c.xyz += texCUBE(_EnvTex,i.refl) * _ReflectionColor * c.a; return c; |
反射凸点高光 |
使用反射烘焙光照 |
Render to Texel
实时光照无疑具有更高品质,但烘焙版本可以获取更大的性能。那么,怎样进行这一操作呢? 一种名为 Render to Texel 的编辑器工具就专为这一目的而创造。请注意:若要使用这一工具,必须运行 Unity 专业版。它通过以下过程将光照烘焙成纹理:
- 通过脚本将切线空间法线贴图转换为自然空间。
- 通过脚本创建自然空间位置贴图。
- 使用之前两种贴图,纹理渲染 (Render to Texture) 整个纹理的全屏途径,每种光照使用另外一种途径。
- 多个不同有利位置的平均结果可以产生一些从各个角度,或者至少从游戏的正常视角来说似乎合理的物体。
这就是最佳图形优化的工作原理。它们通过在编辑器中或在游戏运行前执行,回避了大量计算。一般来说,这就是您想要做的:
%
- 创建精美游戏,而无需担心性能。
- 使用 Unity 的光照贴图 (lightmapper) 等工具和 Render to Texel 以及 子画面包装机 (Sprite Packer) 之类的编辑器扩展就可以将其烘焙成极容易渲染的对象。
- 制作自己的工具是完成这一操作的最佳方式,您可以为游戏出现的任何问题创建完美的工具。
- 创建着色器和脚本,以调整烘焙输出并为其赋予少许“光泽”;创建动态光照的错觉将是一项夺人眼球的效果。
光频率的理念
正如音轨的低音 (Bass) 和高音 (Treble),图像也有高频和低频部分,在渲染时,最好用不同的方式处理,就像立体声使用低音炮和高音扬声器来创造环绕声效果。将图像的不同频率形象化的一种方式是使用 Photoshop 中的“高反差保留 (High Pass)” 滤镜。滤镜 (Filters)->其他 (Other)->高反差保留 (High Pass)。如果您之前曾接触过音频处理,您可能会熟悉高反差保留 (High Pass) 这一名称。本质上说,它的原理是切断所有低于 X 的频率,也就是您传递到滤镜的参数。对于图像,高斯模糊 (Gaussian Blur) 与高反差保留 (High Pass) 具有同样的效果。
这一原理应用到了实时图形中,因为频率非常有助于分离并决定处理方式。例如,在基本的光照贴图环境下,最终图像通过光照贴图的合成获得,这属于低频,但是纹理则属于高频。在 Shadowgun 中,低频光照通过光照探测器快速应用于角色,同时通过使用简单凸点贴图着色器以任意光照方向捏造高频光照。
一般而言,通过使用不同的方法渲染不同的光照频率(例如,烘焙与动态,逐对象与逐级别,逐像素与逐顶点等),可以在有限的硬件上创建内容充实的图像。这是一种风格上的选择,此外,这也是尝试在高频和低频同时拥有更强的变化色彩或值的不错选择。
实践中的频率:Shadowgun 分解
- 顶行
- 超低频高光顶点光照(动态) (Ultra-Low-Frequency Specular Vertex Light (Dynamic)) | 高频 Alpha 通道 (High Frequency Alpha Channel) | 低频光照贴图 (Low Frequency Lightmap) | 高频发射率 (High Frequency Albedo)
- 中间行
- 高光定点光照 (Specular Vertex Light) * Alpha | 高频添加细节 (High Frequency Additive Details ) | 光照贴图 (Lightmap) * 色彩通道 (Color Channel)
- 底行
- 最终效果
请注意: 通常,这些分解指的是延迟渲染中的步骤,但此处不适用。所有这些效果都是一次处理。以下是这个合成场景依靠的两种相关着色器:
+ Show [Lightmapped with Virtual Gloss Per-Vertex Additive] +Shader "MADFINGER/Environment/Virtual Gloss Per-Vertex Additive (Supports Lightmap)" { Properties { _MainTex ("Base (RGB) Gloss (A)", 2D) = "white" {} //_MainTexMipBias ("Base Sharpness", Range (-10, 10)) = 0.0 _SpecOffset ("Specular Offset from Camera", Vector) = (1, 10, 2, 0) _SpecRange ("Specular Range", Float) = 20 _SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 1) _Shininess ("Shininess", Range (0.01, 1)) = 0.078125 _ScrollingSpeed("Scrolling speed", Vector) = (0,0,0,0) } SubShader { Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"} LOD 100 CGINCLUDE #include "UnityCG.cginc" sampler2D _MainTex; float4 _MainTex_ST; samplerCUBE _ReflTex; #ifndef LIGHTMAP_OFF float4 unity_LightmapST; sampler2D unity_Lightmap; #endif //float _MainTexMipBias; float3 _SpecOffset; float _SpecRange; float3 _SpecColor; float _Shininess; float4 _ScrollingSpeed; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; #ifndef LIGHTMAP_OFF float2 lmap : TEXCOORD1; #endif fixed3 spec : TEXCOORD2; }; v2f vert (appdata_full v) { v2f o; o.pos = mul(UNITY_MATRIX_MVP, v.vertex); o.uv = v.texcoord + frac(_ScrollingSpeed * _Time.y); float3 viewNormal = mul((float3x3)UNITY_MATRIX_MV, v.normal); float4 viewPos = mul(UNITY_MATRIX_MV, v.vertex); float3 viewDir = float3(0,0,1); float3 viewLightPos = _SpecOffset * float3(1,1,-1); float3 dirToLight = viewPos.xyz - viewLightPos; float3 h = (viewDir + normalize(-dirToLight)) * 0.5; float atten = 1.0 - saturate(length(dirToLight) / _SpecRange); o.spec = _SpecColor * pow(saturate(dot(viewNormal, normalize(h))), _Shininess * 128) * 2 * atten; #ifndef LIGHTMAP_OFF o.lmap = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw; #endif return o; } ENDCG Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma fragmentoption ARB_precision_hint_fastest fixed4 frag (v2f i) : COLOR { fixed4 c = tex2D (_MainTex, i.uv); fixed3 spec = i.spec.rgb * c.a; #if 1 c.rgb += spec; #else c.rgb = c.rgb + spec - c.rgb * spec; #endif #ifndef LIGHTMAP_OFF fixed3 lm = DecodeLightmap (tex2D(unity_Lightmap, i.lmap)); c.rgb *= lm; #endif return c; } ENDCG } } }
Shader "MADFINGER/Environment/Virtual Gloss Per-Vertex Additive (Supports Lightmap)" { Properties { _MainTex ("Base (RGB) Gloss (A)", 2D) = "white" {} //_MainTexMipBias ("Base Sharpness", Range (-10, 10)) = 0.0 _SpecOffset ("Specular Offset from Camera", Vector) = (1, 10, 2, 0) _SpecRange ("Specular Range", Float) = 20 _SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 1) _Shininess ("Shininess", Range (0.01, 1)) = 0.078125 _ScrollingSpeed("Scrolling speed", Vector) = (0,0,0,0) } SubShader { Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"} LOD 100 CGINCLUDE #include "UnityCG.cginc" sampler2D _MainTex; float4 _MainTex_ST; samplerCUBE _ReflTex; #ifndef LIGHTMAP_OFF float4 unity_LightmapST; sampler2D unity_Lightmap; #endif //float _MainTexMipBias; float3 _SpecOffset; float _SpecRange; float3 _SpecColor; float _Shininess; float4 _ScrollingSpeed; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; #ifndef LIGHTMAP_OFF float2 lmap : TEXCOORD1; #endif fixed3 spec : TEXCOORD2; }; v2f vert (appdata_full v) { v2f o; o.pos = mul(UNITY_MATRIX_MVP, v.vertex); o.uv = v.texcoord + frac(_ScrollingSpeed * _Time.y); float3 viewNormal = mul((float3x3)UNITY_MATRIX_MV, v.normal); float4 viewPos = mul(UNITY_MATRIX_MV, v.vertex); float3 viewDir = float3(0,0,1); float3 viewLightPos = _SpecOffset * float3(1,1,-1); float3 dirToLight = viewPos.xyz - viewLightPos; float3 h = (viewDir + normalize(-dirToLight)) * 0.5; float atten = 1.0 - saturate(length(dirToLight) / _SpecRange); o.spec = _SpecColor * pow(saturate(dot(viewNormal, normalize(h))), _Shininess * 128) * 2 * atten; #ifndef LIGHTMAP_OFF o.lmap = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw; #endif return o; } ENDCG Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma fragmentoption ARB_precision_hint_fastest fixed4 frag (v2f i) : COLOR { fixed4 c = tex2D (_MainTex, i.uv); fixed3 spec = i.spec.rgb * c.a; #if 1 c.rgb += spec; #else c.rgb = c.rgb + spec - c.rgb * spec; #endif #ifndef LIGHTMAP_OFF fixed3 lm = DecodeLightmap (tex2D(unity_Lightmap, i.lmap)); c.rgb *= lm; #endif return c; } ENDCG } } }+ Show [Lightprobes with Virtual Gloss Per-Vertex Additive] +
Shader "MADFINGER/Environment/Lightprobes with VirtualGloss Per-Vertex Additive" { Properties { _MainTex ("Base (RGB) Gloss (A)", 2D) = "white" {} _SpecOffset ("Specular Offset from Camera", Vector) = (1, 10, 2, 0) _SpecRange ("Specular Range", Float) = 20 _SpecColor ("Specular Color", Color) = (1, 1, 1, 1) _Shininess ("Shininess", Range (0.01, 1)) = 0.078125 _SHLightingScale("LightProbe influence scale",float) = 1 } SubShader { Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"} LOD 100 CGINCLUDE #pragma multi_compile LIGHTMAP_OFF LIGHTMAP_ON #include "UnityCG.cginc" sampler2D _MainTex; float4 _MainTex_ST; float3 _SpecOffset; float _SpecRange; float3 _SpecColor; float _Shininess; float _SHLightingScale; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float3 refl : TEXCOORD1; fixed3 spec : TEXCOORD3; fixed3 SHLighting: TEXCOORD4; }; v2f vert (appdata_full v) { v2f o; o.pos = mul(UNITY_MATRIX_MVP, v.vertex); o.uv = v.texcoord; float3 worldNormal = mul((float3x3)_Object2World, v.normal); float3 viewNormal = mul((float3x3)UNITY_MATRIX_MV, v.normal); float4 viewPos = mul(UNITY_MATRIX_MV, v.vertex); float3 viewDir = float3(0,0,1); float3 viewLightPos = _SpecOffset * float3(1,1,-1); float3 dirToLight = viewPos.xyz - viewLightPos; float3 h = (viewDir + normalize(-dirToLight)) * 0.5; float atten = 1.0 - saturate(length(dirToLight) / _SpecRange); o.spec = _SpecColor * pow(saturate(dot(viewNormal, normalize(h))), _Shininess * 128) * 2 * atten; o.SHLighting = ShadeSH9(float4(worldNormal,1)) * _SHLightingScale; return o; } ENDCG Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma fragmentoption ARB_precision_hint_fastest fixed4 frag (v2f i) : COLOR { fixed4 c = tex2D (_MainTex, i.uv); c.rgb *= i.SHLighting; c.rgb += i.spec.rgb * c.a; return c; } ENDCG } } }
Shader "MADFINGER/Environment/Lightprobes with VirtualGloss Per-Vertex Additive" { Properties { _MainTex ("Base (RGB) Gloss (A)", 2D) = "white" {} _SpecOffset ("Specular Offset from Camera", Vector) = (1, 10, 2, 0) _SpecRange ("Specular Range", Float) = 20 _SpecColor ("Specular Color", Color) = (1, 1, 1, 1) _Shininess ("Shininess", Range (0.01, 1)) = 0.078125 _SHLightingScale("LightProbe influence scale",float) = 1 } SubShader { Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"} LOD 100 CGINCLUDE #pragma multi_compile LIGHTMAP_OFF LIGHTMAP_ON #include "UnityCG.cginc" sampler2D _MainTex; float4 _MainTex_ST; float3 _SpecOffset; float _SpecRange; float3 _SpecColor; float _Shininess; float _SHLightingScale; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float3 refl : TEXCOORD1; fixed3 spec : TEXCOORD3; fixed3 SHLighting: TEXCOORD4; }; v2f vert (appdata_full v) { v2f o; o.pos = mul(UNITY_MATRIX_MVP, v.vertex); o.uv = v.texcoord; float3 worldNormal = mul((float3x3)_Object2World, v.normal); float3 viewNormal = mul((float3x3)UNITY_MATRIX_MV, v.normal); float4 viewPos = mul(UNITY_MATRIX_MV, v.vertex); float3 viewDir = float3(0,0,1); float3 viewLightPos = _SpecOffset * float3(1,1,-1); float3 dirToLight = viewPos.xyz - viewLightPos; float3 h = (viewDir + normalize(-dirToLight)) * 0.5; float atten = 1.0 - saturate(length(dirToLight) / _SpecRange); o.spec = _SpecColor * pow(saturate(dot(viewNormal, normalize(h))), _Shininess * 128) * 2 * atten; o.SHLighting = ShadeSH9(float4(worldNormal,1)) * _SHLightingScale; return o; } ENDCG Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma fragmentoption ARB_precision_hint_fastest fixed4 frag (v2f i) : COLOR { fixed4 c = tex2D (_MainTex, i.uv); c.rgb *= i.SHLighting; c.rgb += i.spec.rgb * c.a; return c; } ENDCG } } }
最佳实践
GPU 优化:Alpha–测试
某些 GPU,尤其是移动设备上的 GPU,都会引发 alpha–测试高性能开销(或在像素着色器中使用丢弃 (discard) 和 裁剪 (clip) 操作)。如果可能,应使用 alpha-混合着色器代替 alpha–测试着色器。如果 alpha–测试不可避免,那么应该将可见的经 alpha–测试的像素值保证在最小范围。
iOS 纹理压缩
某些图像(尤其是如果使用 iOS/Android PVR 纹理压缩)倾向于 alpha 通道中的人为视觉效果。在这种情况下,可能需要在图像软件上直接调整 PVRT 压缩参数。可以安装 PVR 导出插件或使用来自 PVRTC 格式创造者 Imagination Tech 的 PVRTexTool。压缩之后的图像文件扩展名为 .pvr,将由 Unity 编辑器直接导入,并保留指定的压缩参数。如果经 PVRT 压缩的纹理不能提供 较好的视觉质量,或者您尤其需要简洁的成像(GUI 纹理可能需要),那么应该考虑使用 16 位纹理而不是 32 位。通过这些操作,将减少一半的内存带宽和存储要求。
Android 纹理压缩
所有支持 OpenGL ES 2.0 的 Android 设备也支持 ETC1 压缩格式;因此,可能的话请尽量使用 ETC1 作为首选纹理格式。
如果针对特定的图形结构,如 Nvidia Tegra 或 Qualcomm Snapdragon,那么应该考虑使用这些结构专用的压缩格式。Android Market 也支持对支持的纹理压缩格式进行过滤,这意味着可以阻止不支持拥有 DXT 压缩纹理等分配存档 (.apk) 的设备下载该程序。
练习(仅限 Unity 专业版)
- 下载 Render to Texel。
- 在模型上烘焙光照。
- 使用 Photoshop 在烘焙之后的图片上运行“高反差保留 (High Pass)” 滤镜。
- 编辑 "Mobile/Cubemapped" 着色器,包括 Render to Texel 资源包,以确保顶点光照代替缺失的低频光照细节。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论