【跟着catlikecoding学渲染#1】 着色器基础

<观前提醒,本文翻译会额外生草加出现错误,有问题请指出,阿里嘎多>

一, Default Scene

当我们在unity中新建一个scene,会有一个默认摄像机和一个定向光,然后嘞,我们创建一个球,可以康到下面这个玩意儿。

1.1Stripping It Down

让我们康康Lighting Setting中的Scene

  • 在环境光中你可以选择天空盒,天空盒就是场景的背景捏
  • 关掉环境光和反射光
  • 然后我们再关掉预计算和实时渲染GI

关闭天空盒后就只剩颜色和基本光照了,like this。

我们关闭天空盒后,可能会想这个球应该变得更暗,但并不是这样的,为什么呢?

  • 背景色是被定义在每个摄像机内的,我们可以打开camera settings康康

让我们进一步简化渲染,关掉方向光组件,你就可以看到一个黑漆麻空(四川方言)的球,但是捏,这个球依然会产生阴影,剩下的是纯色背景,球体轮廓是环境色

1.2 From Object To Image

我们把simple scene拆解成了两部分:

  1. 图像颜色填充在摄像机中的背景颜色中
  2.  球体轮廓显示在上面

How does Unity know that it has to draw a sphere?

  当我们创建一个球体时,这个物体会由一个网格渲染器组件(mesh renderer component)。如果该物体在摄像机的视野中,它就会被渲染,Unity会检测物体的Object's Bounding Box来判断是否渲染

What's a bounding box

在网格里面找到一个最小的box这个就是bounding box ,它是自动生成滴。你可以把边界框视作网格所占体积的简单近似值

然后说一下这个transform 组件,这玩意是拿来改变网格,边界框位置大小和取向的。实际上这个的底层逻辑就是矩阵(MVP变换矩阵)如果对象最终出现在摄像机的视图中,则会安排对其进行渲染

最后,GPU的任务是渲染物体网格,特定的渲染指令由对象的材质定义,材质就叫shader,而shader是一个GPU Program(翻译成项目?),和其他任何一种设定相加。

我们的对象都会有默认的材质

二.Your First Shader

先创建一个shader,然后打开shader file,删除掉内容,从0开始(嗯?哪里有0)然后保存,我们就可以看到紫红色报错

然后cat老师简单的介绍了一些shader的代码构成,因为太过基础这里就跳过了(虽然前面也很基础)

Shader"xxx/My First Shader"{
    subshader{
        //You can use these to group multiple shader variants together
        //This allows you to provide different sub-shaders for different build platforms or levels of detail
       Pass{
       //The sub-shader has to contain at least one pass
       //A shader pass is where an object actually gets rendered
       }
    }
}

然后我们的球就变白啦,因为我们这里使用的一个空白的pass,我们的着色器没有错误,但它是空白的

2.1Shader Programs

现在开始填充内容

Pass{
    CGPROGRAM
        
    ENDCG
}

why are those keywords needed?

shader pass 可以包含着色器程序外其他部分,所以说,程序应该某种方式分离出来

之后我们需要在program中加上顶点和片段的程序,顶点着色器负责处理网格的顶点数据,片元着色器负责对网格三角形内的单个像素进行着色(这里其实还是渲染管线的内容)

CGPROGRAM

#pragma vertex MyVertexProgram
#pragma fragment MyFragmentProgram

ENDCG 

What's a pragma?

pragma这个词来自希腊语,指的是一个动作,或者需要做的事情。它在许多编程语言中用于发出特殊的编译器指令

顶点和片元程序是作为方法编写的,与C#非常相似,尽管它们通常被称为函数。让我们简单地创建两个具有适当名称的空 void 。

CGPROGRAM

#pragma vertex MyVertexProgram
#pragma fragment MyFragmentProgram
void MyVertexProgram () {
}
void MyFragmentProgram () {
}
ENDCG

2.3 Shader Compilation

Unity'的shader不同平台要换成不同的program。 For example, Direct3D for Windows, OpenGL for Macs, OpenGL ES for mobiles,所以说我们处理得不是单个编译器,而是多个。由于编译器不同,所以说平台也不同

在编辑器中选择着色器,然后查看检查器窗口。它显示有关着色器的一些信息,包括当前编译器错误。还有一个已编译的代码条目,带有“编译并显示代码”按钮和下拉菜单。如果单击该按钮,Unity 将编译着色器并在编辑器中打开其输出,以便您可以检查生成的代码。

 

您可以通过下拉菜单选择手动编译着色器的平台。默认设置是为编辑器使用的图形设备进行编译。您也可以针对其他平台手动编译,无论是当前的构建平台、您拥有许可证的所有平台还是自定义选择。这使您能够快速确保着色器在多个平台上编译,而无需进行完整的构建。

For example, here is the resulting code when our shader is compiled for OpenGlCore

// Compiled shader for custom platforms, uncompressed size: 0.5KB

// Skipping shader variants that would not be included into build of current scene.

Shader "Custom/My First Shader" {
SubShader { 
 Pass {
  GpuProgramID 16807
Program "vp" {
SubProgram "glcore " {
"#ifdef VERTEX
#version 150
#extension GL_ARB_explicit_attrib_location : require
#extension GL_ARB_shader_bit_encoding : enable
void main()
{
    return;
}
#endif
#ifdef FRAGMENT
#version 150
#extension GL_ARB_explicit_attrib_location : require
#extension GL_ARB_shader_bit_encoding : enable
void main()
{
    return;
}
#endif
"
}
}
Program "fp" {
SubProgram "glcore " {
"// shader disassembly not supported on glcore"
}
}
 }
}
}

生成的代码被分成两个块,vp和fp,用于顶点和片段程序。但是,在OpenGL的情况下,两个程序最终都位于vp块中。这两个主要函数对应于我们的两个空方法。因此,让我们专注于这些代码并忽略其他代码

#ifdef VERTEX
void main()
{
    return;
}
#endif
#ifdef FRAGMENT
void main()
{
    return;
}
#endif

以下是为 Direct3D 11 生成的代码,精简到有趣的部分。它看起来完全不同,但很明显,代码并没有做太多事情。

Program "vp" {
SubProgram "d3d11 " {
      vs_4_0
   0: ret 
}
}
Program "fp" {
SubProgram "d3d11 " {
      ps_4_0
   0: ret 
}
}

2.4 Including Other Files

基础库,每次需要调用某些文件时可以Unity自带的库

CGPROGRAM
#pragma vertex MyVertexProgram
#pragma fragment MyFragmentProgram

#include "UnityCG.cginc"

void MyVertexProgram () {
}
void MyFragmentProgram () {
}
ENDCG

这个库是和Unity捆绑在一起的着色器包含文件之一

  • 像上图所示的:UnityShaderVariables.cginc 定义了渲染所需的一大堆着色器变量,如变换、摄像机和光源数据。这些都是 Unity 在需要时设置的。
  • HLSLSupport.cginc设置内容,以便无论您定位到哪个平台,都可以使用相同的代码。因此,您无需担心使用特定于平台的数据类型等。
  • UnityInstancing.cginc专门用于实例化支持,这是一种用于减少绘制调用的特定呈现技术。虽然它不直接包含文件,但它取决于Unity Shader Variables.cginc

2.5 Producing Output

然后到了该渲染的部分了,这里顶点程序返回顶点的最终坐标是4*4的,所以说 我们得定义为float4

float4 MyVertexProgram(){
    return 0;
}

当我们输出顶点位置时,我们必须通过SV_POSITION语义附加到我们的代码上,SV表示系统值,POSITION代表最终顶点位置

float4 MyVertexProgram () : SV_POSITION {
				return 0;
			}

同样的,对于片元着色器来讲,因为要输出的是RGBA的值,所以说我们依然得定义为float4

	float4 MyFragmentProgram () {
				return 0;
			}

然后片元程序也需要semantics(不知道怎么翻译),在这种情况下,我们可以用SV_TARGET,这是默认着色器目标,是帧缓冲区,其中包含我们正在生成的图像

float4 MyFragmentProgram () : SV_TARGET {
				return 0;
			}

由于顶点程序的输出将作用于片元程序的输入,所以说,我们需要融合一下

	float4 MyFragmentProgram (
				float4 position : SV_POSITION
			) : SV_TARGET {
				return 0;
			}

Can We omit the position parameter

可以但没必要,因为当涉及多个参数时,容易出事,所以说最好将片元程序的输入与顶点程序输出完全匹配

然后cat老师那OpenGL和D3D11源码比对了一下,得出了即使平台不同,其单位向量也被四分向量所替代

//OpenGl
#ifdef VERTEX
void main()
{
    gl_Position = vec4(0.0, 0.0, 0.0, 0.0);
    return;
}
#endif
#ifdef FRAGMENT
layout(location = 0) out vec4 SV_TARGET0;
void main()
{
    SV_TARGET0 = vec4(0.0, 0.0, 0.0, 0.0);
    return;
}
#endif
//D3D11
Program "vp" {
SubProgram "d3d11 " {
      vs_4_0
      dcl_output_siv o0.xyzw, position
   0: mov o0.xyzw, l(0,0,0,0)
   1: ret 
}
}
Program "fp" {
SubProgram "d3d11 " {
      ps_4_0
      dcl_output o0.xyzw
   0: mov o0.xyzw, l(0,0,0,0)
   1: ret 
}
}

2.6 Transforming Vertices

为了恢复我们的球体,我们的顶点程序必须生成正确的顶点位置。为此,我们需要知道顶点的对象空间位置。我们可以通过向函数添加一个具有 POSITION 语义的变量来访问它。该位置将作为齐次坐标提供

float4 MyVertexProgram (float4 position : POSITION) : SV_POSITION {
				return 0;
			}

再把顶点输出给片元输入

in  vec4 in_POSITION0;
void main()
{
    gl_Position = in_POSITION0;
    return;
}
--------------------
    Bind "vertex" Vertex
      vs_4_0
      dcl_input v0.xyzw
      dcl_output_siv o0.xyzw, position
   0: mov o0.xyzw, v0.xyzw
   1: ret

由于我们使用对象空间信息,它看起来就扭曲了,我们必须将原始顶点位置乘以模型-视图-投影矩阵。此矩阵将对象的变换层次结构与相机变换和投影相结合

4 x 4 MVP 矩阵在 UnityShaderVariables 中定义为UNITY_MATRIX_MVP。我们可以使用 mul 函数将其与顶点位置相乘。这将正确地将我们的球体投影到显示器上。您还可以移动,旋转和缩放它,图像将按预期更改。

float4 MyVertexProgram (float4 position : POSITION) : SV_POSITION {
				return mul(UNITY_MATRIX_MVP, position);
			}

 

三,Coloring Pixels

当我们有正确形状后,就开始添加颜色

float4 MyFragmentProgram (float4 position : SV_POSITION) : SV_TARGET {
    return float4(1, 1, 0, 1);
}

3.1 Shader Properties

着色器单独声明就放再Properties里面,属性名称后面必须跟一个字符串和一个类型,属性声明的最后一部分时默认值的赋值

Properties {
		_Tint ("Tint", Color) = (1, 1, 1, 1)
	}

你可以在面板里面自己设置

3.2 Accessing roperties

如果我们要使用这个属性,我们必须向着色器添加一个变量,名称一定要与属性名相同,然后在片元着色器中返回该变量

#include "UnityCG.cginc"

			float4 _Tint;

			float4 MyVertexProgram (float4 position : POSITION) : SV_POSITION {
				return mul(UNITY_MATRIX_MVP, position);
			}

			float4 MyFragmentProgram (
				float4 position : SV_POSITION
			) : SV_TARGET {
				return _Tint;
			}

这里区别于c#,我们必须先定义变量再去使用它

uniform 	vec4 _Time;
uniform 	vec4 _SinTime;
uniform 	vec4 _CosTime;
uniform 	vec4 unity_DeltaTime;
uniform 	vec3 _WorldSpaceCameraPos;
…
uniform 	vec4 _Tint;
layout(location = 0) out vec4 SV_TARGET0;
void main()
{
    SV_TARGET0 = _Tint;
    return;
}

ConstBuffer "$Globals" 112
Vector 96 [_Tint]
BindCB  "$Globals" 0
      ps_4_0
      dcl_constantbuffer cb0[7], immediateIndexed
      dcl_output o0.xyzw
   0: mov o0.xyzw, cb0[6].xyzw
   1: ret

3.3 From Vertex To Fragment

GPU 通过栅格化三角形来创建图像。它需要三个经过处理的顶点,并在它们之间进行插值。对于三角形覆盖的每个像素,它调用片段程序,传递插值的数据。

因此,顶点程序的输出根本不直接用作片段程序的输入。插值过程介于两者之间。在这里, SV _ POSITION 数据值,但其他东西也可以插值。

	float4 MyFragmentProgram (
				float4 position : SV_POSITION,
				float3 localPosition : TEXCOORD0
			) : SV_TARGET {
				return float4(localPosition, 1);
			}

We're not working with texture coordinates,so why TEXCOORD0

插值数据没有通用语义。每个人都只使用纹理坐标语义来表示插值的所有内容,而不是顶点位置。TEXCOORD0、TEXCOORD1、TEXCOORD2 等。这样做是出于兼容性原因。 还有特殊的颜色语义,但这些很少使用,并且它们并非在所有平台上都可用

编译的片元着色器现在将使用插值数据而不是统一色调。

in  vec3 vs_TEXCOORD0;
layout(location = 0) out vec4 SV_TARGET0;
void main()
{
    SV_TARGET0.xyz = vs_TEXCOORD0.xyz;
    SV_TARGET0.w = 1.0;
    return;
}

当然,顶点程序必须输出本地位置才能正常工作。我们可以通过向其添加一个输出参数来做到这一点,具有相同的 TEXCOORD0 语义。顶点和片段函数的参数名称不需要匹配。这都是关于语义的。

	float4 MyVertexProgram (
				float4 position : POSITION,
				out float3 localPosition : TEXCOORD0
			) : SV_POSITION {
				localPosition = position.xyz;
				return mul(UNITY_MATRIX_MVP, position);
			}

what does .xyz do?

 这称为旋转操作。这就像访问矢量的单个分量一样,但更灵活。您可以使用它来筛选、重新排序和重复浮动组件。例如 .x、.xy、.yx、.xx。在本例中,我们用它来获取位置的前三个分量,忽略第四个分量。所有四个组件都将是 .xyzw。您也可以使用颜色命名约定,例如 .rgba。

额外的顶点程序输出包含在编译器着色器中,我们将看到我们的球体被着色。

in  vec4 in_POSITION0;
out vec3 vs_TEXCOORD0;
vec4 t0;
void main()
{
    t0 = in_POSITION0.yyyy * glstate_matrix_mvp[1];
    t0 = glstate_matrix_mvp[0] * in_POSITION0.xxxx + t0;
    t0 = glstate_matrix_mvp[2] * in_POSITION0.zzzz + t0;
    gl_Position = glstate_matrix_mvp[3] * in_POSITION0.wwww + t0;
    vs_TEXCOORD0.xyz = in_POSITION0.xyz;
    return;
}

3.4 Using Structures

我们可以定义数据结构,它们只是变量的集合。它们类似于C#中的结构,只是语法略有不同。下面是一个结构,用于定义我们要插值的数据。请注意分号在其定义之后的用法。

float4 _Tint;
			
			struct Interpolators {
				float4 position : SV_POSITION;
				float3 localPosition : TEXCOORD0;
			};

			Interpolators MyVertexProgram (float4 position : POSITION) {
				Interpolators i;
				i.localPosition = position.xyz;
				i.position = mul(UNITY_MATRIX_MVP, position);
				return i;
			}

			float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
				return float4(i.localPosition, 1);
			}

3.5 Tweaking Colors

因为负色被钳制为零,我们的球体最终会变得相当暗。由于默认球体的对象空间半径为 1/2,因此颜色通道最终介于 −1/2 和 1/2 之间。我们希望将它们移动到 0–1 范围内,这可以通过向所有通道添加 1/2 来做到这一点。

return float4(i.localPosition + 0.5, 1);

我们还可以通过将其分解到结果中来应用我们的色调。

return float4(i.localPosition + 0.5, 1) * _Tint;

四.Texturing

如果要在不添加更多三角形的情况下向网格添加更多明显的细节和多样性,则可以使用纹理。

  然后,您将图像投影到网格三角形上。 纹理坐标用于控制投影。这些是 2D 坐标对,无论纹理的实际纵横比如何,它们都将覆盖在一个单位的正方形区域中整个图像。水平坐标称为 U,垂直坐标称为 V.因此,它们通常称为 UV 坐标。

U 坐标从左向右增加。因此,图像左侧为 0,中间为 1/2,右侧为 1。V 坐标在垂直方向上以相同的方式工作。它从底部增加到顶部,但 Direct3D 除外,它从上到下

4.1 Using UV Coordinates

Unity 的默认网格具有适合纹理贴图的 UV 坐标。顶点程序可以通过具有 TEXCOORD0 语义的参数访问它们。

	struct VertexData {
				float4 position : POSITION;
				float2 uv : TEXCOORD0;
			};
			
			Interpolators MyVertexProgram (VertexData v) {
				Interpolators i;
				i.localPosition = v.position.xyz;
				i.position = mul(UNITY_MATRIX_MVP, v.position);
				return i;
			}

我们可以通过将 UV 坐标解释为颜色通道,使它们可见,就像局部位置一样。例如,U 变为红色,V 变为绿色,而蓝色始终为 1。

float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
    return float4(i.uv, 1, 1);
}

 Unity 将 UV 坐标环绕其球体,在两极处折叠图像的顶部和底部。您将看到一条从北端延伸到南端的接缝,影像的左侧和右侧连接在一起。因此,沿着该接缝,U 坐标值将同时为 0 和 1。这是通过沿接缝具有重复的顶点来完成的,除了它们的U坐标外,这些顶点完全相同。

4.2 Adding a Texture

贴一张纹理

要使用纹理,我们必须添加另一个着色器属性。常规纹理属性的类型是 2D,因为还有其他类型的纹理。默认值是引用 Unity 默认纹理之一(白色、黑色或灰色)的字符串。

  惯例是将主要纹理命名为_MainTex,因此我们将使用它。这还使您能够使用方便的 Material.mainTexture 属性通过脚本访问它,以防您需要这样做。

Properties {
	_Tint ("Tint", Color) = (1, 1, 1, 1)
	_MainTex ("Texture", 2D) = "white" {}
	}

我们可以通过使用类型为 sampler2D 的变量来访问着色器中的纹理。

float4 _Tint;
sampler2D _MainTex;

使用 tex2D 函数在片段元程序中对具有 UV 坐标的纹理进行采样。

float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
	return tex2D(_MainTex, i.uv);
}

 

发生纹理失真的原因是插值在三角形之间是线性的。Unity 的球体在极点附近只有几个三角形,其中 UV 坐标失真最大。

  因此,UV坐标从一个顶点到顶点以非线性方式变化,但在顶点之间,它们的变化是线性的。结果,纹理中的直线在三角形边界处突然改变方向。

最后我们可以乘上颜色

4.3Tiling and Offset

在向着色器添加纹理属性后,材质检查器不只是添加了纹理字段。它还添加了平铺和偏移控件。但是,更改这些 2D 矢量目前没有任何效果。 这些额外的纹理数据存储在材质中,也可以通过着色器访问。您可以通过与关联材料同名的变量以及_ST后缀来执行此操作。此变量的类型必须为 float4。

Interpolators MyVertexProgram (VertexData v) {
				Interpolators i;
				i.position = mul(UNITY_MATRIX_MVP, v.position);
				i.uv = v.uv * _MainTex_ST.xy;
				return i;
			}

切片矢量用于缩放纹理,因此默认情况下为 (1, 1)。它存储在变量的 XY 部分中。要使用它,只需将其与UV坐标相乘即可。这可以在顶点着色器或片段着色器中完成。在顶点着色器中执行此操作是有意义的,因此我们仅对每个顶点而不是对每个片段执行乘法。

UnityCG.cginc包含一个方便的宏,为我们简化了这个样板。我们可以将其用作方便的速记。

i.uv = TRANSFORM_TEX(v.uv, _MainTex);

What's a macro?

宏类似于函数,只是在预处理步骤中计算,然后再对代码进行实际编译。这允许对代码进行文本操作,例如将_ST附加到变量名称。TRANSFORM_TEX宏使用此技巧。

五.Texture Settings

“环绕模式”指示使用 0–1 范围之外的 UV 坐标进行采样时发生的情况。当包装模式设置为钳位时,UV 被限制为保持在 0–1 范围内。这意味着边缘以外的像素与边缘上的像素相同。当环绕模式设置为重复时,UV 环绕。这意味着边缘以外的像素与纹理另一侧的像素相同。默认模式是重复纹理,这会导致纹理平铺。 如果您没有平铺纹理,则需要改为夹紧 UV 坐标。这可以防止纹理重复,而是复制纹理边界,使其看起来被拉伸。

如果您没有平铺纹理,则需要改为夹紧 UV 坐标。这可以防止纹理重复,而是复制纹理边界,使其看起来被拉伸。

5.1 Mipmaps and Filtering

当纹理的像素(纹素)与它们投影到的像素不完全匹配时,会发生什么情况?存在不匹配,必须以某种方式解决。完成此操作的方式由过滤模式控制。

   最直接的过滤模式是点(无过滤器)。这意味着,当在某些 UV 坐标处对纹理进行采样时,将使用最接近的纹素。这将使纹理具有块状外观,除非纹素精确地映射到显示像素。因此,它通常用于像素完美的渲染,或者当需要块状样式时。

  默认设置是使用双线性筛选。在两个纹素之间的某个位置对纹理进行采样时,将对这两个纹素进行插值。由于纹理是 2D 的,因此这同时沿 U 轴和 V 轴发生。因此,双线性滤波,而不仅仅是线性滤波。

  当纹素密度小于显示像素密度时,此方法有效,因此当您放大纹理时也是如此。结果将看起来很模糊。在相反的情况下,当您缩小纹理时,它不起作用。相邻的显示像素最终将得到相距多个纹理的样本。这意味着将跳过纹理的某些部分,这将导致粗糙的过渡,就像图像被锐化一样。

  这个问题的解决方案是在纹素密度变得太高时使用较小的纹理。显示屏上显示的纹理越小,应使用的版本越小。这些较小的版本称为 mipmaps,是自动生成的。每个连续的 mipmap 的宽度和高度都是上一个级别的一半。因此,当原始纹理大小为 512x512 时,mip 贴图为 256x256、128x128、64x64、32x32、16x16、8x8、4x4 和 2x2。

详见语雀笔记:https://www.yuque.com/docs/share/77524734-d662-429f-8866-09513bea3034?# 《L1.3纹理基础》

如果愿意,可以禁用 mipmap。首先,将“纹理类型类型”设置为“高级”。您可以禁用 mipmap 并应用更改。看到差异的一个好方法是使用像四边形这样的平面物体,并从一个角度看它。

我们可以通过在高级纹理设置中启用淡出Mip贴图来使过渡可见。启用后,检查器中将显示“淡入淡出范围”滑块。它定义了一个 mipmap 范围,超过该范围,mipmap 将转换为纯灰色。通过使此过渡成为一个步骤,您将获得到灰色的急剧过渡。将一步范围向右移动得越远,转换发生的时间就越晚。

What is the use of fading to gray?

它用于细节纹理,我们将在以后的章节中介绍。 您可能认为它可以用于雾效果,但事实并非如此。使用哪种 mipmap 取决于纹素与显示像素密度,而不是 3D 距离。

  要获得此效果的良好视图,请暂时将纹理的 Aniso 级别设置为 0。

  一旦你知道各种mipmaps级别在哪里,你应该能够看到它们之间纹理质量的突然变化。随着纹理投影变小,纹理密度增加,这使得它看起来更清晰。

  直到突然下一个mipmap关卡开始出现,它又变得模糊了。 因此,如果没有mipmap,你就会从模糊到清晰,再到太清晰。使用mipmaps,你会从模糊到清晰,再到突然再次模糊,再到锐利,再到突然模糊,依此类推。

  这些模糊的清晰波段是双线性滤波的特征。您可以通过将滤波器模式切换到三线性来摆脱它们。这与双线性滤波的工作方式相同,但它也会在相邻的 mipmap 级别之间进行插值。因此是三线性的。这使得采样更加昂贵,但它平滑了mipmap级别之间的转换。

另一个有用的技术是各向异性过滤。可能已经注意到,将其设置为 0 时,纹理会变得更加模糊。这与 mipmap 级别的选择有关。

What does anisotropic mean?

粗略地说,当某些东西在不同的方向上看起来相似时,那么它就是各向同性的。例如,无特征的立方体。当情况并非如此时,它是各向异性的。例如,一块木头,因为它的纹理朝一个方向而不是另一个方向。

  当纹理由于透视而以一定角度投影时,您通常最终会发现其中一个维度的失真程度远远超过另一个维度。一个很好的例子是纹理接地层。在远处,纹理的前向后尺寸将比左右尺寸小得多。

   选择哪个 mipmap 级别是基于最差维度。如果差异很大,那么您将获得一个在一个维度上非常模糊的结果。各向异性滤波通过解耦尺寸来缓解这种情况。除了均匀地缩小纹理外,它还提供在任一维度上缩放不同数量的版本。因此,您不仅拥有 256x256 的 mipmap,还有 256x128、256x64 等的 mipmap。

  请注意,这些额外的 mipmap 不像常规 mipmap 那样预先生成。相反,它们是通过执行额外的纹理样本来模拟的。因此,它们不需要更多空间,但采样成本更高。

  各向异性过滤的深度由Aniso Level控制。在 0 时,它被禁用。在 1 时,它将变为启用并提供最小效果。在16岁时,它处于最大值。但是,这些设置受项目质量设置的影响。

  禁用各向异性纹理时,无论纹理的设置如何,都不会发生各向异性过滤。当它设置为“每个纹理”时,它由每个单独的纹理完全控制。也可以将其设置为“强制打开”,这将表现得好像每个纹理的 Aniso 级别至少设置为 9。但是,将“Aniso 级别”设置为 0 的纹理仍不会使用各向异性滤镜。

Summary

本章节主要介绍了一些shader代码,渲染管线和面板信息介绍,下一节就是材质了。

posted @ 2022-05-27 15:52  Naxts  阅读(278)  评论(0编辑  收藏  举报