创建一个Toon风格的着色器

创建一个Toon风格的着色器 **Toon着色器(toon shading)**是游戏中最常使用的效果之一,也被称作(AKA)cel shading(cel是celluloid的缩写[中文也叫 赛璐珞])。它是一种非真实渲染技术,可以让3D模型呈现一种平面效果。许多游戏中用这种着色器把3D的物体渲染成一种手绘物体的效果。下图中你能看到这两者的区别,右边的是标准着色器,左边的是Toon着色器: 始前准备 开始学习这个知识点之前,我们先创建一个着色器和对应的材质球,而且需要导入一个特殊的纹理,步骤如下: 创建一个新的着色器;在这例子中,我们会用上一个知识点的着色器进行扩展 为着色器创建一个新的材质,并且把它应用到3D模型中。 拥有曲面的模型对于toon着色器来说最好。 这个知识点需要一张额外的纹理,叫做ramp贴图[如果要全译,我把它叫梯度贴图]。有一点很重要,在导入的时候把Wrap Mode改为Clamp,如果你想让颜色的边缘变得灵敏,就把Filter Mode设置为Point : 操作步骤 通过下面的步骤我们可以获得toon风格的特殊审美呈现: 添加一个新的叫**_RampTex**纹理属性: _RampTex ("Ramp", 2D) = "white" {} 同时在CGPROGRAM块中添加对应的变量: sampler2D _RampTex; 修改**#pragma指示 ,从而让着色器使用一个叫LightingToon()**的函数: #pragma surface surf Toon 使用下面这个光照模型: fixed4 LightingToon(SurfaceOutput s ,fixed3 lightDir,fixed atten) { half NdotL = dot(s.Normal,lightDir); NdotL = tex2D(_RampTex,fixed2(NdotL,0.5)); fixed4 c; c.rgb = s.Albedo * _LightColor0.rgb*NdotL*atten; c.a = s.Alpha; return c; } 原理介绍 toon风格着色器的主要特征是它的光的渲染方式;表面并非均匀的着色。为了能达这种效果,我们需要一张ramp贴图。他的作用是对Lambertian的光线强度NdotL重新映射,然后把值赋值给另一个值。我们使用一张ramp贴图而不是一个梯度值,是为了强制光线按照步骤渲染。下图展示了ramp贴图是如何纠正光的强度的: 额外内容 我们有很多不同方式来获得toon着色器效果。 我们可以使用不同的ramp贴图来让我们的模型看起来更有吸引力,这就需要你们自己去试试了,然后找到一张你认为最好的。 还有另一种可选方法对纹理进行梯度采样,就是通过对光强度NdotL进行截断取值,这样就只能在0到1的范围内给它赋值特定的值: fixed4 LightingToon(SurfaceOutput s ,fixed3 lightDir,fixed atten) { half NdotL = dot(s....

February 23, 2021 · 1 min · 100 words · Link

理解光照模型

第三章 理解光照模型 在前面的那些章节中,我们介绍了表面着色器并且还理解了如何修改一些物理属性(比如Albedo和Specular)来模拟不同的材质。这些到底是如何工作的呢?每个表面着色器中最重要的部分一一光照模型lighting model。它的功能就是接受这些参数然后计算每一个像素点的最终着色。Unity通常会对开发者隐藏这部分,因为如果想要编写一个光照模型的话,你就必须要去理解光在物体表面是如何反射和折射的。这个章节中我们会毫无保留的向你展示光照模型是如何工作的,并且给你介绍一些你自己创建光照模型所需要的一些基础知识。 这一章中,我们会学习下面所列的知识点: 创建一个自定义的漫反射光照模型 创建一个Toon风格的着色器 创建一个Phong类型类型的高光反射着色器 创建 BlinnPhong 类型的高光反射着色器 创建各向异性类型的高光反射着色器 介绍 想要模拟光的工作方式是一项非常具有挑战性的工作,同时也非常消耗计算资源。在之前的很早一段时间内,游戏中使用的都是一些非常简单的光照模型,效果看起来差到难以置信。尽管现在的3D游戏引擎已经使用了基于物理原理的渲染器,但是有些更简单的光照模型技术还是值得我们去探索的。但有时,我们不得不面对资源紧张的现实,没有办法在这些资源有限的设备上完整实现光照模型,比如我们的移动设备。所以你想在这上面实现自己的光照模型,那么你就很有必要了解这些简单的光照模型。 创建一个自定义的漫反射光照模型 如果你对Unity4很了解的话,你应该知道Unity提供的默认的着色器是基于一个叫Lambertian reflectance的光照模型。我们会在这个知识点向你展示如何创建一个自定义的光照模型,并且解释它后面的数学原理和实现方式。下面的两张图分别展示了标准着色器Standard Shader (右)和diffuse Lambert着色器对同一个几何体进行渲染后,不同的显示效果: 基于Lambertian reflectance光照模型的着色器是典型的非真实渲染着色器;我们现实生活中没有物体会看起来像那样。然而Lambert 着色器依然能在一些低多边形风格的游戏中经常看到,因为跟一些复杂的几何体比起来,它们的三角面数量对比非常明显。用于计算Lambertian反射的光照模型非常高效,这特别适合移动端的游戏。 Unity其实已经提供了光照函数给我们,好让我们能在着色器中使用。它就是Lambertian光照模型。它是光反射模拟的一种更基础更有效率的形式,你能在当今的很多游戏中看到它的存在。 因为它们已经内建在了Unity的表面着色器语言中,所以我从这个开始和基于它开始构建也不失为一个好的选择。你也可以在Unity用户手册中找到一个例子,但我们还是会更深入学习,从而向你解释这些数据是从哪里来的以及为什么它是那样工作的。这些可以为设置光照模型打下一个很好的基础,这些知识将来也能在后面的章节中对我们有帮助。 始前准备 让我们从实现下面几个步骤开始: 创建一个新的着色器并且给它命名好。 创建一个新的材质球,命名好,并且把上一步新建的着色器应用于该材质。 接下来,创建一个球形对象,并且把它大致放在场景中间的位置。 最后,我们创建一个方向光源,让光找到游戏对象上。 当你在Unity中设置好这些资源后, 你就会有一个类似于下图的场景: 操作步骤 Lambertian反射可以在着色器中修改下面的代码实现: 首先在着色器的属性Properties块中添加下面的属性: _MainTex("Texture", 2D) = "white" 修改着色器的**#pragma指示符,从而让着色器使用我们自定义的光照模型,而不是标准Standard**: #pragma surface surf SimpleLambert 使用一个非常简单的表面函数surface function,这个函数仅仅通过它的UV数据对纹理进行采样: void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D(_MainTex,IN.uv_MainTex).rgb; } 添加一个叫做LightingSimpleLambert()的函数,这个函数包含了下面实现Lambertian反射的代码: half4 LightingSimpleLambert(SurfaceOutput s,half3 lightDir,half atten) { half NdotL = dot(s.Normal,lightDir); half4 c; c....

February 22, 2021 · 1 min · 125 words · Link

在地形的表面绘制一个圆

在地形的表面绘制一个圆 在很多的RTS类型的游戏中,通过围绕被选中的单位绘制一个圆圈来表示距离(攻击范围,可移动距离,视野等等)。如果地面是平坦的,那么可以用通过对一张绘制有圆圈的矩形纹理进行缩放就能简单的做到。但是如果地面并不平坦,那么这个矩形的纹理就很有可能被高出的山丘或者其他几何物体遮住。接下来的知识点将展示如何编写一个着色器,让你可以在任何复杂的物体表面绘制一个圆圈[且不会被遮住]。如果你想对这个圆圈移动或者执行动画,那么我们就需要有着色器和C#代码才行。下图展示了用着色器在一个丘陵地形中绘制一个圆圈的例子: 始前准备 这里的着色器主要是用于地形的,对于其他的游戏对象不适用。所以我们首先要做的是在Unity中创建好一个地形。 我们先分别建立一个着色器和材质,名字分别是RadiusShader和RadiusMaterial。 当你的角色物体准备好后;我们会绕着它绘制一个圆圈。 在Unity的菜单[这里的菜单操作我就不翻译了,翻译了感觉反而不好,怕翻译错了,大家找不到],选择 GameObject | 3D Object | Terrain 来创建一个新的地形。 为地形创建一些几何面。你可以导入一个已存在的地形或者自己用地形工具画一个新的。(Raise/Lower Terrain, Paint Height, Smooth Height )[括号里面这些都是Unity地形编辑器中的工具] 地形是Unity中特殊的游戏对象,它表面的纹理映射方式跟传统的3D模型不一样。你不能通过在着色器定义一个**_MainTex来提供纹理,因为在地形中需要直接由地形自己提供。这一步骤可以通过在地形编辑器中选择Paint Texture** 然后点击Add Texture…: 完成上面步骤后,纹理就设置好了。你必须修改地形的材质这样就可以在地形中使用我们提供的自定义的着色器。在 Terrain Settings中, 把Material一栏的属性改为Custom,接着把我们的Radius material材质拖拽到Custom Material栏上。 接下来就要准备你自己的着色器了。 操作步骤 让我们开始编辑着色器RadiusShader 的代码: 在新的着色器中, 添加下面四个属性: _Center("Center", Vector) = (0,0,0,0) _Radius("Radius", Float) = 0.5 _RadiusColor("Radius Color", Color) = (1,0,0,1) _RadiusWidth("Radius Width", Float) = 2 然后在CGPROGRAM块中添加它们各自的变量与之对应: float3 _Center; float _Radius; fixed4 _RadiusColor; float _RadiusWidth; 所以现在输入表面函数的数据不仅仅是纹理的UV数据了,还包括地形的中每一个点的位置(这个位置是基于世界坐标的)。 为了拿到这个参数我们需要修改输入结构体 Input struct,如下所示: struct Input { float2 uv_MainTex; // The UV of the terrain texture float3 worldPos; // The in-world position }; 最后我们在表面函数中使用这个参数: void surf(Input IN, inout SurfaceOutputStandard o) { float d = distance(_Center, IN....

January 16, 2021 · 1 min · 165 words · Link

纹理的压缩和混合

纹理的压缩和混合 纹理的作用并不仅仅只是我们通常认为的保存加载的数据或者像素颜色,同时还有像素点在x和y方向以及RGBA通道的各种设置。我们能把多张图片压缩进一张单独的RGBA纹理中并且使用它们各自的R,G,B和A元素,因为我们可以在着色器中把它们各自纹理中的这些元素分别解压出来。 将各自的灰度图压缩进一张单独的RGBA纹理的结果可以通过下图看出来: 为什么说这会有用呢?在你的应用程序实际消耗的大部分中内存当中,贴图占了很大的一部分。所以如果你想要减少应用程序的大小的话,我们能在着色器中查看所有使用的图片并且想想我们是否能将这些纹理合并到一张单独的纹理中。 任何灰度的纹理都可以压缩进另一张有RGBA通道的纹理。第一次听起来可能有点怪怪的,但我们接下来会用这个知识点来演示纹理压缩的用法并且在我们的着色器中使用这张压缩过的纹理。 举其中一个纹理压缩用法例子,比如你想把一套纹理[有好几张]混合进一张单独的纹理中。这在地形类着色器中很常见,在我们的例子中,我们会用一些排好序的控制纹理或者压缩过的纹理,很好的混合进另一张纹理中。这个知识点会讲到这个技术的,同时还会告诉你如何开始编写好这样一个混合四张纹理的着色器。 始前准备 在我们Unity的着色器文件夹中创建一个新的着色器同时创建一个新的材质与之对应。这两者的命名怎么方便怎么来,不过尽量保证组织和引用方便吧。 建好着色器和材质后,再创建一个新的场景,好给后面做测试。 收集好四张你打算混合在一起的纹理。我们直接用它们展示这几张纹理是如何放到物体表面的。 我们可以用一些非常复杂的混合纹理在地形网格上创建一个非常真实的地形分布纹理,如下所示: 操作步骤 我们通过下面代码来学习如何使用压缩纹理。 我们在着色器的属性Properties块中添加一些属性。我们需要5个sampler2D类型的游戏对象或纹理,2个颜色属性: Properties {_ MainTint ("Diffuse Tint", Color) = (1,1,1,1) //Add the properties below so we can input all of our textures _ColorA ("Terrain Color A", Color) = (1,1,1,1) _ColorB ("Terrain Color B", Color) = (1,1,1,1) _RTexture ("Red Channel Texture", 2D) = ""{} _GTexture ("Green Channel Texture", 2D) = ""{} _BTexture ("Blue Channel Texture", 2D) = ""{} _ATexture ("Alpha Channel Texture", 2D) = ""{} _BlendTex ("Blend Texture", 2D) = ""{} } 接下来我们在**SubShader{}**块中创建一些变量,记住要跟上一步的属性块对应。...

January 14, 2021 · 2 min · 297 words · Link

创建一个有全息效果的着色器

创建一个有全息效果的着色器 近些年来太空主题的发行的越来越多。科幻游戏中很重要的一个部分就是在游戏中集合了来自未来的各种技术。全息投影就是其中的典型代表。全息投影尽管有很多种形式,但是通常用一种半透明,看起来很薄的投影来呈现。这次的这个知识点将会向你展示如何创建一个这样的着色器来模拟这样的效果。我们首先想到:要创建一个优秀的全息投影特效,你需要能够添加噪音,扫描动画和和震动。下图就展示了一个全息投影效果的列子: 始前准备 正如全息投影效果展示的知识物体的轮廓,所以我们可以把我们的这个着色器命名成Silhouette[轮廓的意思]。把它跟材质关联起来并且把它应用到你的3D模型中去。 操作步骤 根据下面的步骤可以将我们的当前的着色器修改为有全息投影效果的着色器: 在着色器中添加下面的属性: _DotProduct("Rim effect", Range(-1,1)) = 0.25 并且添加跟属性对应的变量到CGPROGRAM块中去: float _DotProduct; 因为这个材质是有透明度的,所以需要添加下面的标签: Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" } 注意 根据你将会使用的游戏对象类型,你可能想要它的背面也能看到。如果是这种情况,那么我们就需要在代码中添加Cull Off,从而让模型的背面不会被剔除。 这个着色器并不会尝试去模拟真实世界的材质,所以这里就没有必要再使用PBR关照模型了。我们将会用性能消耗更少的Lambertian 反射来代替它。另外,我们应该使用nolighting来关闭所有的光线并且用alpha:fade来告诉Cg我们得着色器是一个有透明度的着色器: #pragma surface surf Lambert alpha:fade nolighting 修改输入结构体从而能让Unity输入当前的视口方向和世界的法线方向: struct Input { float2 uv_MainTex; float3 worldNormal; float3 viewDir; }; 修改你的表面函数surface function成下面的样子。请记住因为这个着色器使用Lambertian反射作为光照函数,所以表面输出结构体的名字也要相应改成SurfaeOutput,这是SurfaceOutputStandard类型的实例。 void surf(Input IN, inout SurfaceOutput o) { float4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; float border = 1 - (abs(dot(IN....

January 13, 2021 · 1 min · 126 words · Link

创建一个带透明度的材质

创建一个带透明度的材质 到目前为止我们所看到的着色器似乎都有一个共同点——它们都用于了固体材质。如果你想要的提升你的游戏的视觉效果,那么带透明度的材质通常是一个好的开始。它们用途广泛,从火焰效果到窗户的玻璃都会用到它们。但稍微麻烦的是,它们用起来要复杂一点点。在渲染固体模型之前,Unity会根据它们离摄像机的距离(这个叫Z ordering)进行排序,并且跳过渲染所有背朝摄像机的三角面(这个是剔除技术culling)。当渲染带透明度的几何物体时,这些含有两个面的几何物体就会产生问题。这次的知识点将会为你展示:当我们创建有透明度的表面着色器时,我们如何解决这个过程中产生的这些问题。我们在**第六章,片元着色器和抓取通道**我们还会着重回顾这个话题,真实渲染中的玻璃和水体着色器都会涉及到。 始前准备 这个知识点需要一个新的着色器,我们就叫它Transparent吧。同时也需要一个新的材质,这样才能把着色器应用于游戏物体。因为这个物体需要成为一个透明的玻璃窗,那么我们最好用Unity中的quad或者plane来做。我们当然也需要一些不透明的其他游戏对象来对比测试效果。在这个例子中,我们会使用一张PNG图片作为玻璃纹理。这张图片的alpha通道将会用于控制玻璃的透明度。这样的PNG图片大家自行自作,软件不限。但是需要遵守下面的这些步骤: 找一张要用于你窗户玻璃的图片。 用照片编辑软件打开这样图片,比如GIMP或者Photoshop。 选择图片中你想要变成半透明的部分。 在这张图片上创建一个空白(full opacity[抱歉我不懂PS,不知道这个参数的含义])图层 选择上一步创建的图片,以黑色来填充这个图层。 保存图片然后导入到Unity中。 这个知识点中,我们用来试验的图片是一张来自圣斯德望主教座堂 ***Meaux Cathedral in France(https://en.wikipedia.org/wiki/Stained_glass)***的花窗玻璃。 如果遵循了上面的图片制作步骤,那么你也会获得如下类似的一张图片(RGB通道在左图,A通道在右图): 操作步骤 正如我们前面提醒的,我们在使用透明度着色器时需要注意几个方面: 在着色器的SubShader{}代码块中,添加下面的标签告知着色器这是用于透明度的: Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" } 由于这个着色器是为2D 材料设计的,所以确保你的模型的背面不会被绘制,通过添加下面的这个代码: Cull Back 告诉着色器这个材料是半透明的并且需要跟屏幕中绘制的什么内容混合: #pragma surface surf Standard alpha:fade 使用这个表面着色器来决定玻璃的颜色和透明度: void surf(Input IN, inout SurfaceOutputStandard o) { float4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; o.Alpha = c.a; } 原理介绍 这个着色器中介绍了几个新的概念。首先,标签Tags用于添加一些关于游戏对象是如何渲染的信息。但是这次这里真正让人感兴趣的是Queue。Unity默认会根据物体距离摄像机的距离来对游戏对象进行排序。所以当一个物体距离摄像机越近,那么它就会比所有离摄像机更远的物体先绘制。在大多数情况下,它对游戏来说都没有问题,但是有些情况下你可能想要自己控制游戏场景中物体的渲染顺序。Unity已经有提供给我们一些默认的渲染队列,每一个队列都有一个单独的值好让Unity按照这个顺序在屏幕中绘制游戏物体。这些内建的渲染队列分别叫做**[这些参数就不翻译了]** Background,Geometry,AlphaTest,Transparent, 和 Overlay。 这些队列并不是随意创建的;创造它们是为了让我们能更容易的编写着色器代码和跟实时渲染进行交互。下表描述了每一个不同的渲染队列的作用:...

January 12, 2021 · 1 min · 98 words · Link

法线贴图

法线贴图 3D模型的每一个三角面都有一个朝向facing direction,这个朝向就是它向前的指向。它通常用一个垂直并且放置在三角面正中心的一个箭头来表示。这个朝向对于光在表面反射中来说非常的重要。如果相邻的两个三角面朝向不同的方向,那么它们对光的反射也会朝向不同的角度,也就是在着色器中它们的处理方式会不一样。对于曲面物体来说,这里有个疑问:很显然这些拥有曲面的几何体仍然是由平面三角形构成的,那光线改如何处理? 为了避免这个问题,对应三角面的光的反射计算方式此时不根据它的朝向计算,而是根据它的法线方向normal direction方向计算。前面向着色器添加纹理那个知识点讲到,顶点是保存有数据的。法线方向信息也是继UV信息之后,保存在顶点中最有用的信息。这是一个单位长度的向量,并且它表示了顶点的朝向。不考虑朝向的话,那么三角面内的每一个顶点都有它自己的法线方向,只不过这个法线方向是一个存储在顶点中的线性插值。这给了我们用低模模拟高模的能力。下面示例图展示了同一个几何形状在不同的顶点插值密度下的表现。在左边,法线垂直于由顶点表示的面;很明显每个面之间有明显的的割裂感。再看看最右边的几何体,它的法线是通过他的面线性插值得到的,可以看出来的是,尽管它的表面看起来还是很粗糙,但是光线的反射看起来却似乎很光滑。很容易看出来尽管这三个物体的几何体都相同,但是它们的光线反射却不一样。尽管都是由平面三角形构成,但是右边物体的光线反射似乎看起来像曲面反射。 一个有着粗糙的边的光滑物体很明显的表示单位顶点的法线肯定进行了线性插值。如果我们对保存在每个顶点的法线按其方向进行绘制,我们就能够看到它们,正如下图所展示的那样。你应该注意的是每个三角形仅有三条法线,但是相连的三角形有相同的顶点,会看到不止有一条法线从中绘制出来。 法线贴图在计算3D模型的法线技术中脱颖而出。跟纹理贴图类似,法线方向也可以用一张额外的纹理表示,我们把它们叫做法线贴图normal map或者凹凸贴图bump map。 法线贴图通常是一张RGB图片,里面的RGB通常分别用来表示法线方向中的X,Y,Z方向。现在有很多种技术方法来创建一张法线贴图。比如这些应用程序,CrazyBump(http://www.crazybump.com/)跟NDO Painter(http://www.crazybump.com/)可以把2D数据转换成法线数据。其他的应用程序比如Zbrush 4R7(http://www.pixologic.com/)和AUTODESK(http://usa.autodesk.com)可以把雕刻数据转换成法线贴图。如何创建法线贴图完全超出了本书的范畴,但上面的内容对你了解相关相应的知识还是有好处的。 在Unity中向着色器添加法线的过程很简单,因为在表面着色器中有着**UnpackNormals()**这样的方法给你调用。就让我们看看这是怎样的一个过程。 始前准备 分别创建一个新的材质和着色器,并且把它设置到场景视图Scene view中的游戏对象中去。这样的话,我们的项目非常简单,好让我们仅仅是观察法线贴图这项技术。 这个知识点中你需要一张法线贴图,但是我们这本书附带的Unity工程中包含了一张。[当然,你也可以从我这里把这张图片下载下来,如下图] 操作步骤 下面就是创建法线贴图着色器的步骤了: 让我们设置好我们的属性块,从而可以获得颜色和贴图: Properties { _MainTint ("Diffuse Tint", Color) = (1,1,1,1) _NormalTex ("Normal Map", 2D) = "bump" {} } 注意 因为用的是bump来初始化了属性的贴图类型,这等于是告诉了Unity**_NormalTex包含了法线贴图。如果这个贴图没有被设置, 那么会默认给它设置一张灰色的贴图。颜色值会用(0.5,0.5,0.5,1)**,然后看不出一点凹凸感。 在CGPROGRAM下面的**SubShader{}**块中声明下面两个变量,让这两个变量跟属性块中的两个属性关联起来: CPROGRAM #pragma surface surf Lambert // Link the property to the CG program sampler2D _NormalTex; float4 _MainTint; 我们需要修改输入结构体Input struct的名字,从而让我们可以让我们通过模型的UV来访问法线贴图: // Make sure you get the UVs for the texture in the struct struct Input { float2 uv_NormalTex; } 最后,我们通过内建的**UnpackNormal()**函数从法线贴图中提取出我们需要的法线信息。接着,你只要把这些新的法线应用到表面着色器的输出上即可:...

January 9, 2021 · 1 min · 148 words · Link

通过改变UV值来移动纹理

通过改变UV值来移动纹理 在当惊的游戏产业中,一个很常见的游戏纹理技术就是允许你对游戏物体表面的纹理进行滚动。这种技术可以让你创建很多效果,比如瀑布,河流,流动的沿江等等。同时这些技术也是制作动画精灵特效的基础,我们会在这一章节的一系列知识点中来讲解这些内容。 首先,让我们来看看在**表面着色器(Surface Shader)**如何创建一个简单的纹理滚动效果。 始前准备 在这个知识点开始之前,需要你创建一个新的着色器文件和材质。这么做的目的是为了有个干净的着色器,然后我们可以更加方便的学习和观看滚动效果。 操作步骤 闲话少说,我们打开刚才创建的的着色器[着色器的名字文章中没有说,就自己取一个启动的名字吧],然后输入下面每个步骤所展示的代码: 这个着色器需要两个控制纹理滚动的新属性。所以我们添加一个速度属性控制X方向的滚动,添加另一个速度属性控制Y方向的滚动,如下面的代码所示: Properties { _MainTint("Diffuse Tint",Color) = (1,1,1,1) _MainTex ("Base (RGB)", 2D) = "white" {} _ScrollXSpeed("X Scroll Speed",Range(0,10)) = 2; _ScrollYSpeed("Y Scroll Speed",Range(0,10)) = 2; } 修改CGPROGRAM代码块中的Cg属性中的变量,创建新的变量[把原来的都删掉,用下面展示的代替],这样我们就能访问来自着色器属性的值了: fixed4 _MainTint; fixed _ScrollXSpeed; fixed _ScrollYSpeed; sampler2D _MainTex; 修改表面函数surface function从而修改传递给tex2D()函数的UV值。然后,使用内建的_Time变量来对UV进行循环播放的动画,这样的话当我们点击Unity中的运行按钮的时候,我们就能看到动画效果了: void surf (Input IN, inout SurfaceOutputStandard o) { // Create a separate variable to store our UVs // before we pass them to the tex2D() function fixed2 scrollUV = IN....

January 9, 2021 · 1 min · 191 words · Link

向着色器添加纹理

向着色器添加纹理 通过纹理,可以很容易让着色器变得生动起来,获得非常真实的效果。为了更有效的使用纹理,我们需要了解一张2D图片是如何映射到3D模型中去的。 这个映射的过程称之为纹理贴图texture mapping,为了完成映射,我们在使用的模型和着色器上还有额外的工作。模型实际上是由很多的三角形拼接而成的;而三角形的每个顶点都保存有着色器可以访问的各种数据。 其中很重要的一个信息就是UV信息 (UV data)。 它包含两个坐标,U和V,其取值范围是0到1。这两者表示2D图片的像素点坐标的XY位置信息,而这些信息将会映射到顶点中去。 UV数据只为顶点表示[意思可能也等价于:UV数据只存在顶点中]; 当三角内的点需要被纹理贴图时,GPU会插值最接近的UV值,从而从相应的纹理中找到正确的像素点。下面的图片展示了一张2D纹理贴图到3D模型中的三角形中的情况: UV数据保存在3D模型中并且需要3D模型工具去编辑它们。有些模型缺少UV组件,因而它们不支持纹理贴图。比如3D模型软件中的默认的那个兔子模型,就没有提供这么一个组件。 始前准备 学习这个知识点的时候,你需要一个有UV数据和纹理的3D模型。然后把它们都导入到Unity中。也可以直接拖拽到Unity编辑器中,会自动导入。因为标准着色器支持默认的纹理贴图。我们会用到这一点,而后会详细的介绍它是如何工作的。 操作步骤 用标准着色器给你的模型添加一张纹理异常的简单,按照下面步骤: 创建一个叫TexturedShader标准着色器。 创建一个名为TexturedMaterial的材质球。 通过拖拽的方式,把着色器赋值给材质,把着色器拖到材质上即可。 选择刚才的材质,然后拖拽模型对应的纹理到一个叫**Albedo(RGB)的矩形区域中的空白部分。如果你正确的执行了上述步骤,你的材质检查器面板(Inspector )**会如下图所示: 原理介绍 当通过材质的检查器面板使用标准材质的时候,纹理贴图背后的处理过程对于开发者来说是透明的。如果我们想了解它是如何工作的,那我们需要更加详细的了解我们刚才创建的TexturedShader着色器。在着色器的属性Properties部分,我们可以看到**Albedo (RGB)**的纹理跟代码的关联如下代码所示: MainTex: _MainTex ("Albedo (RGB)", 2D) = "white" {} 在我们着色器代码中的CGPROGRAM代码块部分,纹理被定义为sampler2D类型,这是一种标准的2D纹理类型: sampler2D _MainTex; 紧接着下一行给我们展示了Input这个结构。这个结构就是surface 函数中得输入参数并且这个结构包含了一个叫做uv_MainTex的包组数组: struct Input { float2 uv_MainTex; }; 每一次调用**surf()函数的时候,对应3D模型中的包含_MainTex 这个UV的Input 结构都需要被渲染。标准着色器会知道uv_MainTex 跟_MainTex **是关联的,并且会自动初始化它。如果你真的很想了解到底UV是怎么从3D模型映射到2D纹理的话,你可以看看第三章, 理解光照模型。 终于,UV数据被用来在**surface **函数中展示成一张纹理: fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; 注意 U和V的取值范围都是从0到1,**(0,0)和(1,1)**相当于两个相对的角[可以想象成一个是左下角,一个是右上角]。如果你的纹理出现了颠倒的情况,试着把V的值也颠倒就能解决了。 额外内容 当你把纹理导入到Unity的时候,你就会默认设置一些sampler2D类型将要使用的一些属性。 最重要的就是筛选模式Filter mode,它决定了一张纹理显示的时候颜色是如何插值的。跟UV数据会准确的指向像素的正中心非常不同;在一些其他的情况中,你也许想对最近的像素之间进行颜色插值从而获得更一致的颜色。下图是示例纹理在检查器面板Inspector tab的截屏: 当以一个很大的倾斜角去观看一张纹理时,纹理采样似乎会呈现一种看起来不舒服的人工制品。你可以通过设置更高的**Aniso Level **的值来减少这种感觉。这一点对地板纹理和天花板纹理特别有用,可以解决一些小瑕疵导致的纹理观感不连续性的问题。 相关补充 如果你想了解更多关于纹理时如何映射到3D模型表面的内部工作原理,你可以通过下面的网址了解相关信息[可能需要用梯子才能访问]:...

December 11, 2020 · 1 min · 73 words · Link

使用包组数组

使用包组[包装组织] 笼统的讲,显示器上每一个像素点都至少会执行一次。这也是为什么GPU要设计高度优化的并行架构的原因。同样的在Cg语言的标准变量类型和操作符中,这种设计哲学也很明显。理解它们,不仅仅是为了正确的使用着色器,同时也是为了能够写出更高效的着色器。 操作步骤 在Cg语言中有两种类型的变量:单精度值single和包组packed arrays。后者很容易辨别因为这种类型通常会以数字结尾,比如float4,int4等等。正如它们的名字所表示的一样,这些类型的变量跟我们编程语言中的结构体structs类似,这也意味着每一个这样的变量包含了多个单精度值。在Cg语言中我们称之为包组packed arrays,尽管它们并非真的是传统意义上的数组。 在包组中的元素能像常见的结构体那样访问。通常它们表示成x,y,z 和w 。然而Cg语言还有另一种表示,就是r,g,b,a。尽管你使用x或者r去表示都是可以的,但是对于代码阅读者来说它们之间的区别就非常的大了。事实上,在着色器编程中,经常涉及到的就是位置和颜色的计算。你可能对下面的标准着色器代码中的代码片段还有印象吧: o.Alpha = _Color.a; 在这里,o是一个结构体而**_Color就是一个包组。这也是为什么Cg要禁止上面提到的两种表示进行混用的原因:你不能使用_Color.xgz**。 这里还有一个很重要的包组的特性,这种特性在C#中没有:swizzling[这个不知道怎么翻译]。Cg允许仅通过简单的一行代码就对包组内的元素进行寻址和重新排序。又是下面在标准着色器中熟悉的代码片段: o.Albedo = _Color.rgb; Albedo 是一个 fixed3类型,也就是说它里面包含了三个fixed类型的值。 然而**_Color** 是一个fixed4类型的定义。由于**_Color**定义包含的元素比 Albedo定义包含的元素要多,直接赋值的话,由于不匹配,肯定会产生一个编译错误。如果用C#代码来进行同样的操作,代码如下所示: o.Albedo.r = _Color.r; o.Albedo.g = _Color.g; o.Albedo.b = _Color.b; 相比于C#代码,在Cg语言中,我们可以用如下代码简写: o.Albedo = _Color.rgb; Cg语言也允许对元素进行重新排序。比如,通过**_Color.bgr**这个代码去交换红色和蓝色通道的颜色。 最后要讲一点,当一个单精度值赋值给包组时,这个值会被复制到包组的所有元素中去: o.Albedo = 0; // Black =(0,0,0) o.Albedo = 1; // White =(1,1,1) 这个就是Cg语言中的smearing特性。 Swizzling还可以被用作表达式的左值,不过仅当包组的具体元素能被这样使用: o.Albedo.rg = _Color.rg; 上面这种特性,叫做masking. 压缩矩阵 真正发挥swizzling特性潜力的是在它应用于压缩矩阵的时候。Cg语言允许像float4x4这种类型,这是一个四行四列的矩阵。你可以使用***_mRC标记访问矩阵中的单个元素,R表示元素所在的行而C*表示元素所在的列: float4x4 matrix; // ... float first = matrix._m00; float last = matrix._m33; _mRC标记还可以接连使用:...

December 9, 2020 · 1 min · 90 words · Link