一点心得


  • 首页

  • 归档

Unity Shaders and Effects Cookbook总结

发表于 2018-04-03

Phong和BlinnPhong反射类型光照模型

Phong

Phong模型是基于光照(图中1 Light向量)的方向和用户的视角(图1 View向量)方向进行计算的。通过计算Light的反射向量与用户的视角方向向量的向量积作为光照的强度因子。

计算公式:

代码方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//lightDir就是图1的Light向量,viewDir就是图1的View向量
fixed4 LightingPhong(SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten)
{
	//Reflection
  	float NdotL = dot(s.Normal - lightDir);
	float3 reflectionVector = normalize(2.0 * Normal * NdotL - Light);
    
  	//Specular
  	float spec = pow(max(0, dot(reflectionVector, viewDir)), _SpecPower);
  	float3 finalSpec = _SpecularColor.rgb * spec;
  
  	fixed4 color;
  	color.rgb = (s.Albedo * _LightColor0.rgb * max(0, NdotL) * atten) + (_LightColor0.rgb * finalSpec);
  	color.a = s.Alpha;
  
  	return color;
}

BlinnPhong

BlinnPhong可以看作是堆Phong模型的简化版,它是通过视角方向(View)和光照方向(Light)构成的半角向量(Half)来计算光照的。直接使用半角向量而不用计算光照的反射向量的方式更加高效。

计算公式:

代码方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//lightDir就是图1的Light向量,viewDir就是图1的View向量
fixed4 LightingPhong(SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten)
{
  	float NdotL = dot(s.Normal - lightDir);
  
    float3 halfVector = normalize(lightDir + viewDir);
  	float NdotH = max(0, dot(s.Normal, halfVector));
  	float spec = pow(NdotH, _SpecPower), _SpecularColor);
  
  	fixed4 color;
  	color.rgb = (s.Albedo * _LightColor0.rgb * max(0, NdotL) * atten) + (_LightColor0.rgb * finalSpec);
  	color.a = s.Alpha;
  
  	return color;
}

挤压效果

这种挤压的效果的原理是将顶点沿发现方向进行投影,用代码表示就是这样:

1
v.vertex.xyz += v.normal * _Amount;

_Amount 是挤压的因子

还可以通过额外添加一个纹理(或者使用主要纹理的alpha通道)来表示挤压的程度:

1
2
3
4
5
6
7
8
sampler2D _ExtrusionTex;
void vert(inout appdata_full v)
{
	float4 tex = tex2Dlod(_ExtrusionTex, float4(v.texcoord.xy, 0, 0));;
	float extrusion = UnpackNormal(tex.r);
	
	v.vertex.xyz += v.normal * _Amount * extrusion;
}

从顶点修改器中采样一个纹理,应该使用texDlod而不是tex2D。

抓取功能

原理是结合vertex和fragment着色器以及抓取通行技术,然后对抓取纹理进行采样,在对其UV值做一点修改来制作出一些细微的变形效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Pass {
  #pragma vertex vert
  #pragma fragment frag
  #include "UnityCG.cginc"
  
  //抓取通行会自动创建一个纹理,然后通过这个变量可以引用到
  sampler2D _GrabTexture;
  
  struct vertInput {
    float4 vertex : POSITION;
  };
  
  struct vertOutput {
    float4 vertex : POSITION;
    float4 uvgrab : TEXCOORD1;
  };
  
  vertInput vert(vertexInput v) {
    vertexOutput o;
    o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
    //获取到抓取的UV
    o.uvgrab = ComputeGrabScreenPos(o.vertex);
    return o;
  }
  
  half4 frag(vertexOutput i) : COLOR {
    //通过抓取的UV来采样抓取纹理的值
    fixed4 col = tex2Dproj(_GrabTexture, Unity_PROJ_COORD(i.uvgrab));
    return col + half4(0.5, 0, 0, 0);
  }
}
阅读全文 »

Unity渲染流程简述

发表于 2018-01-14

unity 4.7.1f1

之前了解Unity的setpass call和batches时把顺道把渲染这块的代码(4.7.f1)也阅读了下,先利用一个思维导图把渲染的几个大的步骤记下来,以便有个整体的脉络(这里只记录Forward渲染)。

注1:由Camera的投影矩阵和对象的Z轴向量的积得出

阅读全文 »

UGUI的Graphic Rebuild问题

发表于 2017-12-09

今天在项目中发现了一个UI的性能消耗比较异常的问题。在Unity的Profiler里面我看到一项Canvas.SendWillRenderCanvasesCPU消耗持续比较高,但是查看游戏的UI界面好像动态变化的UI并没有。在逐个关闭UI才定位到有一张图片一直在做渐变效果,所以才导致UI一直在重建。这里写个简单的例子模拟下,在场景中创建一张Image,然后挂载一个测试脚本,在这个脚本里面一直更新这个Image的alpha通道就可以看到这种现象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TestGraphicRebuild : MonoBehaviour
{
    public Image FadeImage;
    private float _mAlpha = 0;

	void Update ()
	{
	    _mAlpha += 0.01f;
	    _mAlpha = _mAlpha > 1 ? 0 : _mAlpha;
	    
        FadeImage.color = new Color(
                              FadeImage.color.r, 
                              FadeImage.color.g, 
                              FadeImage.color.b, 
                              _mAlpha);
	}
}
阅读全文 »

C# 泛型集合的Boxing问题

发表于 2017-12-05

今天检查项目中代码的Boxing问题的时候。有一个点当时让我困惑了不少时间。如下:

1
2
3
4
5
6
7
8
9
public IEnumerator<float> Func0()
{
  yield return 0.1f;
}

public IEnumerator Func1()
{
  yield return 0;
}

这里在实际代码运行过程中Func1会产生Boxing而Func0没有产生Boxing,按照自己浏览的C#的文档对Boxing的理解这里应该都是会产生Boxing的才对。下面是C#的Boxing文档说明的:

Boxing is the process of converting a value type to the type object or to any interface type implemented by this value type. When the CLR boxes a value type, it wraps the value inside a System.Object and stores it on the managed heap.

查看了IEnumerator的定义:

1
2
3
4
public interface IEnumerator<T> : IEnumerator, IDisposable
{
  T Current {get;}
}

是interface类型,那么上面的函数中yield return 0.1f中的0.1f这个float类型的变量转换为IEnumerator为什么不会Boxing呢? 先查看下两个方法的IL代码,对比下IL代码的区别。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//对于Func0
.class nested private auto ansi sealed beforefieldinit '<Func0>c__Iterator0'
	...
{
	...
	// Fields
	.field assembly float32 $current //current编译成为了我们指定的float类型
	.field assembly bool $disposing
	.field assembly int32 $PC	
	...
}

//对于Func1
.class nested private auto ansi sealed beforefieldinit '<Func1>c__Iterator1'
	...
{
	...
	// Fields
	.field assembly object $current//current编译成为了默认的object类型
	.field assembly bool $disposing
	.field assembly int32 $PC
}

那么问题就很清楚了,Func0其实每次迭代的时候接收值的变量就是float类型(对应于IL的float32类型)的所以根本不需要转换类型。但是对于Func1函数,编译成IL代码存储当前值的变量是object类型的,所以当我们Func1中返回值为0的int类型时,这个int型的变量会被转换成object类型而导致Boxing。

其实正确的操作是先看C#关于泛型的文档才对。不过现在看下也不迟。其中第一段就有如下描述:

by using a generic type parameter T you can write a single class that other client code can use without incurring the cost or risk of runtime casts or boxing operations

到这儿疑问就比较明确的解决了。

阅读全文 »

Unity游戏的图形渲染优化(译)

发表于 2017-12-03

原文Optimizing graphics rendering in Unity games。这里对这篇文章的粗略翻译,当作自己的笔记。

介绍

在这篇文章中我们将会学习Unity在渲染一帧的时候其内部的发生的事情,在渲染的时候会发生什么类型的性能问题同时我们怎样去处理这些性能问题。

在阅读这篇文章之前,必须明白没有一个同样的尺度来提高渲染的性能问题。在我们游戏中影响性能问题的原因有很多同时非常依赖游戏运行的硬件和操作系统。通过调查,实验和细心的分析实验结果来解决性能问题是我们最重要的方式。

这篇文章包含的信息主要是大部分常见的性能问题的修改建议和一些更深层次的阅读链接。我们的游戏中可能也存在这其中的一个问题或者一些问题或者都没有包含。但是这篇文章仍然将帮助我们理解我们自己的问题同时给予我们去搜索我们问题的知识和线索。

渲染的简介

在我们开始介绍之前,让我们快速简单看下Unity渲染一帧时其内部发生了什么。理解事件流和正确的术语将会帮助我们 理解,调查和解决我们的性能问题。

注:在这篇文章中,我们将会使用”对象(object)”术语来表示一个我们游戏的渲染的对象。任何渲染组件的GameObject将会被统称为对象。

在最基本的层面上,渲染可以描述为一下几步:

  • CPU计算出必须渲染的对象和决定怎么去绘制。
  • CPU发送指令到GPU。
  • GPU根据CPU的指令来确定绘制的内容。

现在让我们来更近一步看看究竟发生了什么。这篇文章后面将会详细的介绍上面的每一个步骤。但是现在让我们先来熟悉下文中使用的词语和理解CPU和GPU在渲染中扮演的不同角色。

描述渲染的词语通常用渲染管线(rendering pipeline),记住这是一个非常有用的情景。高效的渲染都是关于保持更高信息的传送能力。

对于每一个渲染的帧,CPU会做如下的工作:

  • CPU会检查场景中的每一个对象来决定这个对象是否需要被渲染。只有当这个对象满足特定的条件的时候才会被渲染;例如,它的包围盒的一些部分在相机视锥体内。一个对象如果将不会被渲染我们称之为这个对象被裁剪了(culled)。关于相机的视锥体和视锥体裁剪的更多信息可以看着这篇文章。
  • CPU搜集每一个将会被渲染的对象的信息然后将这些数据分类放入叫做draw calls命令中。一个draw call包含了一个mesh和这个mesh怎样被渲染的数据;例如,这个mesh用哪一张贴图。基于这些情况,共享设置的图像会合并到一些相同的draw call中。合并不同对象的数据到同一个draw call中我们称之为batching。
  • CPU为每次draw call创建一个叫batch的数据包。batchs有可能包含了draw calls之外的数据,但是这种情况不像常见的一个性能问题,因此我们不会在这篇文章里面来讨论。

对于每个draw call包含的batchs,CPU现在必须要入如下的工作:

  • CPU可以向GPU发送一个命令来改变一些被统称为渲染状态的变量。这个命令叫做SetPass call。SetPass call告诉GPU哪个设置被用来渲染下一个mesh。只有当上一个mesh渲染完成下一个mesh准备渲染的时候需要改变渲染状态的时候才需要发送SetPass call命令。
  • CPU发送draw call命令到GPU。draw call会指示GPU使用上一次设置好的SetPass call去渲染制定的mesh。
  • 基于这些情况,batchs的数据被渲染可能需要不止一个pass。一个pass是一段着色器的代码,使用一个新的pass需要改变渲染状态。对于一个batchs中的每个pass,CPU必须发送一个新的SetPass call命令同时必须再次发送draw call命令。这个pass才会生效。

同时,GPU会做如下的工作:

  • GPU按照CPU发送的顺序来处理CPU发送的任务。
  • 如果当前的任务是SetPass call,GPU就更新渲染状态。
  • 如果当前的任务是draw call, GPU就渲染这个draw call的mesh。这个是分阶段进行的,每个阶段单独定义在着色器代码中。这部分渲染比较复杂我们不会详细的讨论,但是这部分对于我们去理解顶点着色器(vertex shader)这部分代码和片段着色器(fragment shader)这部分代码非常有用。顶点着色器是用来告诉GPU怎么去处理mesh的顶点数据的代码,片段着色器使用来怎样去处理每个单独像素代码。
  • GPU重复处理从CPU发送过来的任务,直到所有的任务都处理完成。

现在我们理解了当Unity渲染一帧时其内部发生了什么,接下来让我们思考下当渲染发生的时候会随之发生什么问题。

阅读全文 »
1 … 4 5 6 … 9
© 2025 yiliangduan@qq.com