朝日

ZhaoriGame

面剔除(Culling)

​ 一个3DCube我们最多只能看到3个面,那多余的几个面就能丢弃它,能节省片段着色器的执行数,这就是面剔除(Culling),我们可以分析顶点数据的环绕顺序(Winding Order)来区分正面和背面,而反面在绝大多数情况下是不用渲染的。

在Unity中Cull有三种用于控制几何体的哪一面会被剔除

Cull Back || Front || Off

  • Cull Back——不渲染多边形的背面(默认)。
  • Cull Front——不渲染多边形的正面。这将看到原来被正面遮挡的背面内容。
  • Cull Off——禁用剔除,两个面都会被绘制。同于一些特殊的效果。(一般用于绘制比较薄的对象,如:纸)
默认情况下,逆时针顶点所定义的三角形将会被处理为正向三角形。

观察者所面向的所有三角形顶点就是我们所指定的正确环绕顺序了,而立方体另一面的三角形顶点则是以相反的环绕顺序所渲染的。这样的结果就是,我们所面向的三角形将会是正向三角形,而背面的三角形则是背向三角形。如下图:

​ 在顶点数据中,我们将两个三角形都以逆时针顺序定义(正面的三角形是1、2、3,背面的三角形也是1、2、3(如果我们从正面看这个三角形的话))。然而,如果从观察者当前视角使用1、2、3的顺序来绘制的话,从观察者的方向来看,背面的三角形将会是以顺时针顺序渲染的。虽然背面的三角形是以逆时针定义的,它现在是以顺时针顺序渲染的了。这正是我们想要剔除(Cull,丢弃)的不可见面了

混合(Blending)

混合(Blending)通常是实现物体透明度(Transparency)的一种技术。

在Unity中提供了渲染队列来实现透明效果,使用SubShader的Queue标签决定渲染队列,索引越小越早渲染。

Background 索引 效果
Background 1000 最先绘制,通常绘制背景
Geometry 2000 默认
AlphaTest 2450 需要透明度测试使用此队列
Transparent 3000 从后往前渲染,透明度混合使用此队列
Overlay 4000 实现叠加效果

Unity中实现物体透明分全透明和半透明两种:

  1. 透明度测试(全透明),给定一个值,不满足条件的都将被舍弃

    函数: void clip (float x)

    例如:

    1
    2
    3
    4
    clip(float x);
    if(x<0.5f){ //舍弃小于0.5的片元
    discard;
    }
  2. 透明度混合(半透明),Blend是Unity提供的设置混合模式的命令。想要实现半透明的效果就需要把当前自身的颜色和已经存在的颜色缓冲中的颜色值进行混合。

    一般的混合都是通过以下方程来实现:

    • C source:源颜色向量。这是源自纹理的颜色向量。
    • C destination:目标颜色向量。这是当前储存在颜色缓冲中的颜色向量。
    • F source:源因子值。指定了alpha值对源颜色的影响。
    • F destination:目标因子值。指定了alpha值对目标颜色的影响。

    例如:要实现红和绿两种颜色的混合

    结果就是重叠方形的片段包含了一个60%绿色,40%红色的一种脏兮兮的颜色:

​ 要想让混合在多个物体上工作,我们需要最先绘制最远的物体,最后绘制最近的物体。普通不需要混合的物体仍然可以使用深度缓冲正常绘制,所以它们不需要排序。但我们仍要保证它们在绘制(排序的)透明物体之前已经绘制完毕了。当绘制一个有不透明和透明物体的场景的时候,大体的原则如下:

  1. 先绘制所有不透明的物体。
  2. 对所有透明的物体排序。
  3. 按顺序绘制所有透明的物体。

在Unity中为了得到透明物体的排序我们需要开启深度写入,但这会使透明无法进行,所以需要两个Pass来渲染,第一个开启深度写入,但不输出颜色,第二个Pass进行正常的透明度混合。

1
2
3
4
5
6
7
Pass{
ZWrite On
ColorMask 0
}
Pass{
//混合颜色
}

  • 相同的材质,属性不同,避免创建新的材质

    MaterialPropertyBlock

1
2
3
4
5
6
//会创建一个新的材质
meshRenderer.material.color = clolr;
//不会创建一个新的材质
var propertyBlock = new MaterialPropertyBlock();
propertyBlock.SetColor("_Color", color);
meshRenderer.SetPropertyBlock(propertyBlock);

深度测试相关(ZTest)

在不使用深度测试的时候,如果我们先绘制一个距离较近的物体,再绘制距离较远的物体,则距离远的物体因为后绘制,会把距离近的物体覆盖掉,这样的效果并不是我们所希望的。而有了深度缓冲以后,绘制物体的顺序就不那么重要了,都能按照远近(Z值)正常显示,这很关键。

深度

深度为该像素点离摄像机的距离(z值),存在深度缓冲中,通常会以16、24、32位float来存储,精度越高越精确。通常为24位

深度测试

将该像素点的z值对比G-Buffer的值,通过则更新为新的深度值,测试失败则丢弃该片段

深度缓冲

深度缓冲中存着深度数据,由于深度缓冲是在片段着色器运行后在屏幕空间进行,现在大部分的GPU都提供一个叫做提前深度测试(Early Depth Testing)的硬件特性。提前深度测试允许深度测试在片段着色器之前运行。只要我们清楚一个片段永远不会是可见的(它在其他物体之后),我们就能提前丢弃这个片段。

深度值精度

深度缓冲包含一个介于0.0和1.0之前的值,里面存着深度值为距离摄像机的距离,但几乎永远不会使用这样的线性深度缓冲(Linear Depth Buffer)的。要想有正确的投影性质,需要使用一个非线性的深度方程,它是与 1/z 成正比的。它做的就是在z值很小的时候提供非常高的精度,而在z值很远的时候提供更少的精度。这样才能更大的利用深度值的精度。

由于非线性方程与 1/z 成正比,在1.0和2.0之间的z值将会变换至1.0到0.5之间的深度值,这就是一个float提供给我们的一半精度了,这在z值很小的情况下提供了非常大的精度。在50.0和100.0之间的z值将会只占2%的float精度,这正是我们所需要的。这样的一个考虑了远近距离的方程是这样的:

变成图大概是这样

深度冲突

一个很常见的错误是俩个平面紧密贴在一起时,深度缓冲没有足够的精度来决定谁在前面,就会出现交替闪烁的现象,叫深度冲突(Z-fighting)

抗深度冲突技术中,最简单的就是使用更高的精度深度缓冲,会牺牲掉一些性能,却能获得更好的效果。

简单的UI框架,方便以后复制

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace IL
{

public enum UILevel
{
Bottom,//底层
Common, //普通层
Top,//最前层
}


public class UIMgr : ASingleton<UIMgr>
{



private Dictionary<string, PanelView> mAllPanel = new Dictionary<string, PanelView>();

private Transform mBottomTrans;
private Transform mCommonTrans;
private Transform mTopTrans;

private Canvas mCanvas;
private CanvasScaler mCanvasScaler;
private GraphicRaycaster mGraphicRaycaster;
private RectTransform mRectTransform;

private EventSystem mEventSystem;

private Transform UIRoot;


public void Init()
{
UIRoot = GameObject.Instantiate(ResMgr.Ins.Load<GameObject>(AssetBundleName.UI,"uiroot")).transform;
GameObject.DontDestroyOnLoad(UIRoot);

mBottomTrans = UIRoot.Find("Bottom");
mCommonTrans = UIRoot.Find("Common");
mTopTrans = UIRoot.Find("Top");

mCanvas = UIRoot.GetComponent<Canvas>();
mCanvasScaler = UIRoot.GetComponent<CanvasScaler>();
mGraphicRaycaster = UIRoot.GetComponent<GraphicRaycaster>();
mRectTransform = UIRoot.GetComponent<RectTransform>();

mEventSystem = UIRoot.Find("EventSystem").GetComponent<EventSystem>();

}

public RectTransform RectTransform
{
get { return mRectTransform; }
}

public GraphicRaycaster GraphicRaycaster
{
get { return mGraphicRaycaster; }
}

public Canvas RootCanvas
{
get { return mCanvas; }
}
/// <summary>
/// 设置分辨率
/// </summary>
/// <param name="width"></param>
/// <param name="height"></param>
public void SetResolution(int width, int height)
{
mCanvasScaler.referenceResolution = new Vector2(width, height);
}


private T OpenUI<T>(string PanelName, UILevel uiLevel) where T:PanelView
{
if (!mAllPanel.ContainsKey(PanelName))
{
CreateUI(typeof(T), PanelName, uiLevel);
}
mAllPanel[PanelName].SetActive(true);
return mAllPanel[PanelName]as T;

}

private void OpenUIAsync<T>(string PanelName, Action<UnityEngine.Object> onLoaded = null) where T : PanelView, new()
{
if (!mAllPanel.ContainsKey(PanelName))
{
CreateUIAsync<T>(PanelName,onLoaded);
}
onLoaded(null);
// return mAllPanel[PanelName];

}

private void CreateUIAsync<T>(string panelName, Action<UnityEngine.Object> onLoaded) where T : PanelView, new()
{
ResMgr.Ins.LoadAsync(AssetBundleName.UI, panelName,onLoaded);
}

private void CreateUI(Type type, string panelName, UILevel uiLevel)
{
PanelView panelView = Activator.CreateInstance(type) as PanelView;

Transform parent;

switch (uiLevel)
{
case UILevel.Bottom:
parent = mBottomTrans;
break;
case UILevel.Common:
parent = mCommonTrans;
break;
case UILevel.Top:
parent = mTopTrans;
break;
default:
parent = mCommonTrans;
break;
}
GameObject ui = GameObject.Instantiate(ResMgr.Ins.Load<GameObject>(AssetBundleName.UI, panelName), parent);
ui.name = panelName;
panelView.SetGameObject(ui);
panelView.Init();
mAllPanel.Add(panelName, panelView);

}

public ViewType Open<ViewType>(UILevel uiLevel = UILevel.Common) where ViewType : PanelView
{
return OpenUI<ViewType>( GetName<ViewType>(), uiLevel);
}

public void OpenAsync<T>( Action<UnityEngine.Object> onLoaded = null)
{
OpenUIAsync<PanelView>(GetName<T>(), onLoaded);
}

private string GetName<T>()
{
if (Runtime.Ins.IsHotResProject)
{
string name = typeof(T).ToString();
return name.Replace("Type : ", "");
}
else
{
string name = typeof(T).ToString();
string[] nameSplits = name.Split('.');
return nameSplits[nameSplits.Length - 1];
}


//return name;
}
}
}

  • 颜色向量(Color Vector):一个通过红绿蓝(RGB)分量的组合描绘大部分真实颜色的向量。一个物体的颜色实际上是该物体所不能吸收的反射颜色分量。
  • 冯氏光照模型(Phong Lighting Model):一个通过计算环境光,漫反射,和镜面光分量的值来估计真实光照的模型。
  • 环境光照(Ambient Lighting):通过给每个没有被光照的物体很小的亮度,使其不是完全黑暗的,从而对全局光照进行估计。
  • 漫反射着色(Diffuse Shading):一个顶点/片段与光线方向越接近,光照会越强。使用了法向量来计算角度。
  • 法向量(Normal Vector):一个垂直于平面的单位向量。
  • 法线矩阵(Normal Matrix):一个3x3矩阵,或者说是没有平移的模型(或者模型-观察)矩阵。它也被以某种方式修改(逆转置),从而在应用非统一缩放时,保持法向量朝向正确的方向。否则法向量会在使用非统一缩放时被扭曲。
  • 镜面光照(Specular Lighting):当观察者视线靠近光源在表面的反射线时会显示的镜面高光。镜面光照是由观察者的方向,光源的方向和设定高光分散量的反光度值三个量共同决定的。
  • 冯氏着色(Phong Shading):冯氏光照模型应用在片段着色器。
  • Gouraud着色(Gouraud shading):冯氏光照模型应用在顶点着色器上。在使用很少数量的顶点时会产生明显的瑕疵。会得到效率提升但是损失了视觉质量。
  • GLSL结构体(GLSL struct):一个类似于C的结构体,用作着色器变量的容器。大部分时间用来管理输入/输出/uniform。
  • 材质(Material):一个物体反射的环境光,漫反射,镜面光颜色。这些东西设定了物体所拥有的颜色。
  • 光照属性(Light(properties)):一个光的环境光,漫反射,镜面光的强度。可以使用任何颜色值,对每一个冯氏分量(Phong Component)定义光源发出的颜色/强度。
  • 漫反射贴图(Diffuse Map):一个设定了每个片段中漫反射颜色的纹理图片。
  • 镜面光贴图(Specular Map):一个设定了每一个片段的镜面光强度/颜色的纹理贴图。仅在物体的特定区域显示镜面高光。
  • 定向光(Directional Light):只有一个方向的光源。它被建模为不管距离有多长所有光束都是平行而且其方向向量在整个场景中保持不变。
  • 点光源(Point Light):一个在场景中有位置的,光线逐渐衰减的光源。
  • 衰减(Attenuation):光随着距离减少强度的过程,通常使用在点光源和聚光下。
  • 聚光(Spotlight):一个被定义为在某一个方向上的锥形的光源。
  • 手电筒(Flashlight):一个摆放在观察者视角的聚光。
  • GLSL uniform数组(GLSL Uniform Array):一个uniform值数组。它的工作原理和C语言数组大致一样,只是不能动态分配内存。

  • OpenGL: 一个定义了函数布局和输出的图形API的正式规范。
  • GLAD: 一个拓展加载库,用来为我们加载并设定所有OpenGL函数指针,从而让我们能够使用所有(现代)OpenGL函数。
  • 视口(Viewport): 我们需要渲染的窗口。
  • 图形管线(Graphics Pipeline): 一个顶点在呈现为像素之前经过的全部过程。
  • 着色器(Shader): 一个运行在显卡上的小型程序。很多阶段的图形管道都可以使用自定义的着色器来代替原有的功能。
  • 标准化设备坐标(Normalized Device Coordinates, NDC): 顶点在通过在剪裁坐标系中剪裁与透视除法后最终呈现在的坐标系。所有位置在NDC下-1.0到1.0的顶点将不会被丢弃并且可见。
  • 顶点缓冲对象(Vertex Buffer Object): 一个调用显存并存储所有顶点数据供显卡使用的缓冲对象。
  • 顶点数组对象(Vertex Array Object): 存储缓冲区和顶点属性状态。
  • 索引缓冲对象(Element Buffer Object): 一个存储索引供索引化绘制使用的缓冲对象。
  • Uniform: 一个特殊类型的GLSL变量。它是全局的(在一个着色器程序中每一个着色器都能够访问uniform变量),并且只需要被设定一次。
  • 纹理(Texture): 一种包裹着物体的特殊类型图像,给物体精细的视觉效果。
  • 纹理缠绕(Texture Wrapping): 定义了一种当纹理顶点超出范围(0, 1)时指定OpenGL如何采样纹理的模式。
  • 纹理过滤(Texture Filtering): 定义了一种当有多种纹素选择时指定OpenGL如何采样纹理的模式。这通常在纹理被放大情况下发生。
  • 多级渐远纹理(Mipmaps): 被存储的材质的一些缩小版本,根据距观察者的距离会使用材质的合适大小。
  • stb_image.h: 图像加载库。
  • 纹理单元(Texture Units): 通过绑定纹理到不同纹理单元从而允许多个纹理在同一对象上渲染。
  • 向量(Vector): 一个定义了在空间中方向和/或位置的数学实体。
  • 矩阵(Matrix): 一个矩形阵列的数学表达式。
  • GLM: 一个为OpenGL打造的数学库。
  • 局部空间(Local Space): 一个物体的初始空间。所有的坐标都是相对于物体的原点的。
  • 世界空间(World Space): 所有的坐标都相对于全局原点。
  • 观察空间(View Space): 所有的坐标都是从摄像机的视角观察的。
  • 裁剪空间(Clip Space): 所有的坐标都是从摄像机视角观察的,但是该空间应用了投影。这个空间应该是一个顶点坐标最终的空间,作为顶点着色器的输出。OpenGL负责处理剩下的事情(裁剪/透视除法)。
  • 屏幕空间(Screen Space): 所有的坐标都由屏幕视角来观察。坐标的范围是从0到屏幕的宽/高。
  • LookAt矩阵: 一种特殊类型的观察矩阵,它创建了一个坐标系,其中所有坐标都根据从一个位置正在观察目标的用户旋转或者平移。
  • 欧拉角(Euler Angles): 被定义为偏航角(Yaw),俯仰角(Pitch),和滚转角(Roll)从而允许我们通过这三个值构造任何3D方向。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

namespace GameKit
{
public class Joystick : MonoBehaviour
{

[Header("摇杆最大半径(UGUI)")]
public float maxRadius = 0;
[Header("摇杆最小半径(UGUI)")]
public float minRadius = 0;
[Header("摇杆框")]
public Transform stickBorder;
[Header("摇杆")]
public Transform stick;

/// <summary>
/// 绑定的相机
/// </summary>
[HideInInspector]
public Camera camera;

/// <summary>
/// 触摸的起始位置
/// </summary>
Vector2 _touchStartPos;

/// <summary>
/// 摇杆起始位置
/// </summary>
Vector3 _stickBorderInitPos;

/// <summary>
/// 当Stick的值改变时触发
/// </summary>
public event Action<Vector2> onValueChange;

List<KeyCode> _pressedKeyCode = new List<KeyCode>();

Vector2 _lastValue;

bool _isStickMode = false;

CanvasGroup stickBorderGroup;

void Start()
{
_stickBorderInitPos = stickBorder.position;
stickBorderGroup = stickBorder.gameObject.GetComponent<CanvasGroup>();
}


private void FixedUpdate()
{
if (_isStickMode == false)
{
CheckKeyPress(KeyCode.UpArrow);
CheckKeyPress(KeyCode.DownArrow);
CheckKeyPress(KeyCode.LeftArrow);
CheckKeyPress(KeyCode.RightArrow);

CheckKeyRelease(KeyCode.UpArrow);
CheckKeyRelease(KeyCode.DownArrow);
CheckKeyRelease(KeyCode.LeftArrow);
CheckKeyRelease(KeyCode.RightArrow);

Vector2 tempValue = Vector2.zero;
if (_pressedKeyCode.Count > 0)
{
switch (_pressedKeyCode[0])
{
case KeyCode.UpArrow:
tempValue = Vector2.up;
break;
case KeyCode.DownArrow:
tempValue = Vector2.down;
break;
case KeyCode.LeftArrow:
tempValue = Vector2.left;
break;
case KeyCode.RightArrow:
tempValue = Vector2.right;
break;
}
}

SetValue(tempValue);
}
}

void SetValue(Vector2 value)
{
if (_lastValue != value)
{
_lastValue = value;
onValueChange?.Invoke(_lastValue);
}
}

void CheckKeyPress(KeyCode keyCode)
{
if (Input.GetKeyDown(keyCode))
{
_pressedKeyCode.Remove(keyCode);
_pressedKeyCode.Insert(0, keyCode);
}
}

void CheckKeyRelease(KeyCode keyCode)
{
if (!Input.GetKey(keyCode))
{
_pressedKeyCode.Remove(keyCode);
}
}

/// <summary>
/// 得到指定GameObject下,鼠标相对的localposition坐标
/// </summary>
/// <param name="go"></param>
/// <returns></returns>
Vector2 GetLocalMousePosition(GameObject go)
{
if(null == camera)
{
Debug.LogError("Joystick need binding a camera");
}

Vector2 screenMouse = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
Vector2 localPoint;
RectTransformUtility.ScreenPointToLocalPointInRectangle(go.GetComponent<RectTransform>(), screenMouse, camera, out localPoint);

//Debug.LogFormat("Mouse:{0} Screen:{1} LocalPoint:{2}", Input.mousePosition, screenMouse, localPoint);
return localPoint;
}

/// <summary>
/// 触摸开始的时候
/// </summary>
/// <param name="e"></param>
public void OnPointerDown(BaseEventData e)
{
stickBorder.localPosition = GetLocalMousePosition(gameObject);

stickBorderGroup.alpha = 0.4f;
}

/// <summary>
/// 滑动开始的时候
/// </summary>
/// <param name="e"></param>
public void OnBeginDrag(BaseEventData e)
{
_isStickMode = true;

_touchStartPos = GetLocalMousePosition(stickBorder.gameObject);
}

/// <summary>
/// 滑动中
/// </summary>
/// <param name="e"></param>
public void OnDrag(BaseEventData e)
{
Vector2 touchNowPos = GetLocalMousePosition(stickBorder.gameObject);

var moveVector = (touchNowPos - _touchStartPos);

if (moveVector.magnitude <= minRadius)
{
return;
}

moveVector = Vector3.ClampMagnitude(moveVector, maxRadius);

stick.localPosition = moveVector;

Vector2 value = new Vector2(moveVector.x, moveVector.y);

SetValue(value);
}

/// <summary>
/// 滑动结束的时候
/// </summary>
/// <param name="e"></param>
public void OnEndDrag(BaseEventData e)
{
stick.localPosition = Vector3.zero;
onValueChange?.Invoke(Vector2.zero);
_isStickMode = false;

ResetStickBorder();
}

void ResetStickBorder()
{
stickBorderGroup.alpha = 0.0f;
stickBorder.position = _stickBorderInitPos;
}
}
}

  1. 《C++必知必会》

渲染流水线

坐标转换依次顺序

Object space 模型空间

World space 世界坐标系空间

Eye space 观察坐标系空间

Clip and Project space 屏幕坐标空间

注意:

  1. 光照计算通常在World coordinate space(世界坐标空间)里计算,也可以在Eye space 里计算。
  2. 顶点法向量属于Object space ,转化为World space时,要通过(world matrix)转置矩阵的逆矩阵来转换 (复习线性代数去了)

Eye Space

以Camera为原点 ,由视线方向、视角和远近平面共同组成一个梯形三维空间,称之为viewing frustum(视锥),超出部分会被裁剪 frustum culling(视锥裁剪)

Project and clip space

因为在不规则的体(viewing frustum)中进行裁剪并非易事,所以应该是先投影再裁剪具体分为三个步骤:

  1. 用透视变换矩阵把顶点从视锥体中变换到裁剪空间的CVV中;
  2. 在CVV进行图元裁剪;
  3. 屏幕映射:将进过前述过程得到的坐标映射到屏幕坐标系上。
  • 在第一个步骤里的过程为“投影”,主要投影方法有两种:正交投影和透视投影。
  • 只有图元完全或部分存在于视锥内部时才需要光栅化。超出部分进行裁剪。
  • 视点去除可以不用在GPU中进行,可以使用高级语言在CPU上实现,提前可减去GPU负担。

Primitive Assemble&&Triangle setup

  • Primitive Assembly,图元装配,即将顶点根据Primitive(原始的连接关系),还原出网格结构。
  • 涉及到三角形的顶点顺序(三角形的法向量朝向)根据右手来决定三角面片的法向量(逆时针排列),法向量朝向视点为正,如果为反面进行背面去除操作(Back-face-Culling)。
  • 所有的裁剪剔除都是为了减少需要绘制的顶点个数。
  • 裁剪算法主要包括:视域剔除(View Frustum Culing)、背面剔除(Back-Face Culling)、遮挡剔除(Occlusing Culling)和视口裁剪等。

光栅化

目前我们拿到了每个点的屏幕坐标值(Screen coordinate),也知道我们需要绘制的图元(点、线、面)但是有两个问题:

  1. 点的屏幕坐标都是浮点数,像素都是由整数表示。(绘制的位置为接近两指定端点的实际线段位置如(10.48,20.51)转化为(10,21)四舍五入 或 加0.5取整)。
  2. 在屏幕上需要绘制的有点、线、面,如何根据两个已经确定位置的2个像素点绘制一条线段,如何根据已经确定了位置的3个像素点绘制一个三角形面片。(区域填充推荐慕课课程大力点击进入

Pixel Operation

片元操作:计算出每个像素的颜色值,包括

  1. 被遮挡面通过一个被称为深度测试的过程而消除。

  2. Texture operatioin,纹理操作,根据像素的纹理坐标,查询对应的纹理值。

  3. Blending,混合,根据目前已经画好的颜色,与正在计算的颜色的透明度混合为两种颜色,作为新的颜色输出,通常称之为alpha混合技术。屏幕上的每个像素都关联一个RGB颜色值和一个Z缓冲器深度值,alpha值(可以根据需要生成并存储)。

    从渲染管线得到的RGBA,使用over操作符进行混合:

    a是透明度值(alpha)Ca表示透明物体的颜色,Cs表示混合前像素的颜色,Cd为最终计算得到的颜色。

    为了绘制透明物体,需要对物体进行排序,用z buffer 首先绘制不透明物体,然后从后往前混合透明物体。

  4. Filtering,将正在算的颜色经过某种Filtering(滤波)后输出,可以理解为:经过一种数学运算变成新的颜色值。(如最近邻滤波和线性滤波)

最终像素的颜色写入帧缓存,过程如下图

0%