unity shader development[2]

56 篇文章 3 订阅

你的第一个 Unity 着色器

  在上一章中,我们介绍了开发实时光照着色器所需的许多概念。在本章中,我将带您开始在 Unity 中进行着色器开发的实用方面。您将安装 Unity(如果您尚未安装)并了解着色器编辑工作流程在 Unity 中的工作原理。为此,我们将创建一个基本项目并编写一个简单的着色器,这样您就可以将上一章中学到的一些东西付诸实践。

Unity 简介

  为了编写着色器,您需要一个游戏引擎或渲染器。在本书中,我们将使用 Unity 游戏引擎作为渲染器。您还可以为其他游戏引擎、图形 API 和各种 3D 建模软件程序(如 Maya、Blender 等)编写着色器,但这超出了本书的范围,尽管适用相同的原则。如果您已经知道 Unity 的工作原理并安装了它,请随时跳过本章的这一部分。

  从阴影的角度来看,光的类型实际上也是一个重要的选择,而不仅仅是从艺术角度来看。在 Unity 中,您只能实时地在点光源和平行光之间进行选择,但我们稍后将讨论区域光源以及为什么它们对于基于物理的渲染很重要。您可以通过在 Hierarchy 面板中右键单击并选择 Light ➤ Directional Light 来添加灯光。您还可以通过单击灯光游戏对象并在检查器中更改灯光类型来更改灯光类型。

  现在我们需要创建一个材质。材质是包含与某种表面相关的所有设置的文件。这包括将使用哪个着色器来渲染它、任何颜色、值、要传递给着色器的纹理以及其他数据。让我们创建一个。

  首先在 Project 面板中创建一个新目录并将其命名为 Materials。然后,右键单击“材料”目录并选择“创建”➤“材料”(参见图 2-4)。调用新材料 RedMaterial
在这里插入图片描述

  现在您需要立即创建 Shaders 目录。右键单击它并选择 Create ➤ Shader ➤ Unlit Shader(见图 2-5)。称之为 RedShader。您可以创建几种不同类型的着色器,但 Unlit 类型是最简单的

在这里插入图片描述
  现在您需要将着色器分配给材质并将材质应用到球体。第一个任务是通过单击“项目”面板中的材料文件来完成的。现在查看 Inspector 面板以自定义此材质。在最顶部,您可以选择它应该使用的着色器。默认情况下,任何新材质都使用 Unity 标准着色器。要找到您创建的着色器,请导航到 Unlit ➤ RedShader(参见图 2-6)。
在这里插入图片描述
  现在将材质从 Project 面板拖动到 Hierarchy 面板中的 Cube GameObject 或场景查看器中的立方体模型上。你会看到立方体会改变外观并变成一个完全白色的立方体,没有任何阴影(见图 2-7)。

在这里插入图片描述
  让我们看看 RedShader 属性。单击 Cube GameObject,Inspector 将显示它的属性(见图 2-8)。在 Mesh Renderer 中,请注意有一个 Materials 列表,其中有一个成员,您的 RedMaterial。在网格渲染器下方,您将找到材质属性。指定的着色器应该是 Unlit/RedShader;应该有一个地方可以分配纹理,目前是空的。然后您将看到 Tiling、Offset 和 RenderQueue 选项。
在这里插入图片描述

着色器编辑

  您将其命名为 RedShader,但目前它更像是一个白色着色器。让我们深入研究代码并修复它。清单 2-1 显示了着色器的完整代码,因为 Unity 会默认填充它。它可以分解为各个部分,我们将在代码之后列出。

  更改此设置将更改您必须通过的路径,在材质中,将这个着色器分配给它。着色器的文件名和路径可以不同(这可能非常危险),因此更改文件名不会更改着色器路径,反之亦然。

Properties
Properties    {
        _MainTex ("Texture", 2D) = "white" {}
}

  检查器中显示的每个属性都在此处声明,以及一些不需要声明的属性。在这种情况下,只声明了一个纹理,所以我们只能向这个着色器传递一个纹理。您可以根据需要声明任意数量的属性,但是如果它们超出了目标平台的能力,着色器编译器就会抱怨。

Sub-Shaders
SubShader
{

  一个着色器中可以有多个子着色器,它们有几种类型。加载着色器时,Unity 将使用 GPU 支持的第一个子着色器。每个子着色器都包含一个渲染通道列表。我们将在有关图像效果的章节中回到这一点。

Tags
Tags { "RenderType"="Opaque" }

  标签是可以表达信息的键/值对,比如使用哪个渲染队列。透明和不透明的游戏对象在不同的​​渲染队列中渲染,这就是代码指定“不透明”的原因。我们将回到标签,但我们现在不需要更改它们。

Passes
Pass
{

  每个通道都包含设置渲染和实际着色器计算代码的信息。可以从 C# 脚本一个一个单独地执行传递。

CGPROGRAM (and ENDCG)
CGPROGRAM

  CGPROGRAM 和 ENDCG 标记命令的开始和结束。

Pragma 语句
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog

  这些提供了一种设置选项的方法,例如顶点着色器和像素着色器应该使用哪些函数。这是将信息传递给着色器编译器的一种方式。一些编译指示可用于自动编译同一着色器的不同版本。

Includes
#include "UnityCG.cginc"

  需要包含以使此着色器编译的“库”文件。 Unity 中的着色器“库”相当广泛,而且文档很少。这里我们只包含 UnityCG.cginc 文件。

输出和输入结构
struct appdata
{   
	float4 vertex : POSITION;
	float2 uv : TEXCOORD0;
v2f
{
	float2 uv : TEXCOORD0;
    UNITY_FOG_COORDS(1)
    float4 vertex : SV_POSITION;
};

  如第 1 章所述,顶点着色器通过结构将信息传递给片段着色器。 v2f 就是这个文件中的那个结构。顶点着色器可以通过输入结构请求特定信息,这里是 appdata。

  分号后的词,如SV_POSITION,称为语义。它们告诉编译器我们想要在结构的特定成员中存储什么类型的信息。 SV_POSITION 语义,当附加到顶点着色器输出时,意味着该成员将包含顶点在屏幕上的位置。

  您会看到其他带有前缀 SV 的语义,它代表系统值。这意味着它们指的是管道中的特定位置。这种区别已在 DirectX 版本 10 中添加;在此之前,所有语义都是预定义的。

变量声明
sampler2D _MainTex;
float4 _MainTex_ST

  在属性块中定义的任何属性都需要在CGPROGRAM块中再次定义为具有适当类型的变量。在这里,_MainTex属性被适当地定义为一个sampler2D,随后在顶点和片段函数中使用。

顶点函数和片段函数

在这里插入图片描述
  根据pragma语句#pragma vertex name和#pragma fragment name的定义,可以选择shader中的任意一个函数作为顶点或片段shader,但需要符合一些要求,我们将在后面的章节中列出.
  现在,您将通过使此着色器符合其名称 RedShader 来更加熟悉编辑

着色器编辑

  为了习惯着色器编辑,我们将首先进行一些简单的编辑并删除对最终结果没有贡献的代码。

从白色到红色

  我们要把网格的最终颜色改成红色。双击着色器文件,MonoDevelop(或Visual Studio,取决于你的偏好)应该为你打开该文件。它应该显示带有语法色彩的文件,这将使它更容易阅读。
  让我们考虑一下——我们可以做些什么来使这个着色器输出变为红色——然后我们将清理将成为未使用的代码。如果你考虑一下我们在第 1 章中经历的渲染管道,你会意识到如果你在片段函数的末尾硬编码你想要的颜色,它返回 col,你基本上会覆盖任何其他完成的计算达则。清单 2-2 向您展示了如何做到这一点。
在这里插入图片描述
  col 变为 fixed4(1,0,0,1)。 fixed4 是一种包含四个具有固定精度的十进制数的类型。 fixed 不如 half 精确,而 half 不如 float 精确。
  在这种情况下,我们选择哪种精度并不重要,但是如果您想在不破坏保真度的情况下从着色器中获得更多性能,这将很重要。在这个位置,作为着色器输出的最终颜色,向量的第一个分量是红色,第二个是绿色,第三个是蓝色,第四个是 alpha 值。请记住,Alpha 通常会被忽略,除非您在透明队列中进行渲染。
  我删除了片段函数中的大部分代码,因为它不再相关。保持整洁并注意只在着色器中留下实际有用的代码是值得的。更进一步,我们可以消除与雾渲染有关的任何事情,因为我们对最终颜色进行了硬编码。最终的着色器,删除了所有多余的计算和选项,看起来像清单 2-3。
在这里插入图片描述
在这里插入图片描述
  如您所见,与纹理和雾相关的任何内容都已被删除。剩下的部分负责通过首先计算顶点的位置将三角形光栅化为像素。光栅化部分是不可见的,因为它是在 GPU 中实现的,并且它不可编程。

  您可能还记得我们在前一章中提到了许多坐标系。在顶点函数中,顶点位置从对象空间直接转换到剪辑空间。这意味着顶点位置已从 3D 坐标空间投影到不同的 3D 坐标空间,这更适合数据将进行的下一组计算。 UnityObjectToClipPos 是执行此转换的函数。你现在不需要理解这一点,但你会一次又一次地遇到坐标空间,所以继续注意它们是值得的。

  下一步(自动发生)是将剪辑空间顶点位置传递给 GPU 的光栅化功能(位于顶点和片段着色器之间)。光栅化器的输出将是属于片段的内插值(像素位置、顶点颜色等)。

  这个包含在 v2f 结构中的内插数据将被传递给片段着色器。片段着色器将使用它来计算每个片段的最终颜色。

添加属性

  硬编码值不是一个好习惯,所以让我们把这个红色着色器转换成我们想要的任何颜色的着色器。为此,我们需要重新引入属性块。但是我们想要一个颜色属性而不是纹理属性。

  第一步是将此属性块添加回文件:

Properties
{    
	_Color ("Color", Color) = (1,0,0,1)
}

  属性块由 _Name (“Description”, Type) = 默认值组成。有许多不同类型的属性,包括纹理、颜色、范围和数字。我们将在未来介绍更多。现在,让我们继续将此属性连接到着色器本身。现在,着色器将编译,但不会使用您选择的颜色。
  那是因为我们还没有声明和使用 _Color 变量。首先在 CGPROGRAM 语句之后的某处添加声明:

fixed4 _Color;

  然后更改片段函数中的 return 语句,使其实际使用 _Color 变量:

fixed4 frag (v2f i) : SV_Target
{    
	return _Color;
}

  现在您可以从 Material Inspector 面板中选择一种颜色(见图 2-9)。

在这里插入图片描述
  现在您知道如何将属性连接到着色器代码中。该着色器与所有 Unity 着色器一样,混合了两种语言。一个是 Cg,一种由 NVIDIA 开发的着色器语言,用于 CGPROGRAM 和 ENDCG 语句之间。另一个是ShaderLab,是Unity开发和使用的Cg的扩展,是CGPROGRAM之外的一切。这就是您必须声明 _Color 变量的原因,因此代码的 Cg 部分将知道位于 ShaderLab 一侧的属性。
  现在着色器名称不再具有描述性。让我们将其更改为 MonochromeShader。要做到这一点,你必须改变:

  • 着色器文件名为 MonochromeShader
  • Unlit/MonochromeShader 的着色器路径名

  您可以选择是否使它们匹配,但是当您有许多着色器时,名称不匹配会很快变得混乱。

总结

  本章介绍了 Unity 中的着色器编辑工作流程,包括着色器代码如何映射到 GPU 上的渲染管道。您制作了一个非常简单的着色器,其中包括顶点和片段函数,并且您还学习了很多 ShaderLab 语法。

下一篇

  现在您已经掌握了一些着色编码经验,下一章将更详细地介绍着色器如何适应图形管道。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值