本文内容参考《Unity 5.x Shaders and Effects Cookbook》


表面着色器和材质贴图

总的来说,一个表面着色器有两个关键的步骤。第一,必须确定材质的物理属性,如漫反射颜色、光滑度和透明度,这些属性在surface function中被初始化并被保存在一个叫surface output的结构体。第二,surface output被传给一个光照模型。这个特殊的函数会结合场景中的附近的光。然后这两个参数会被用来计算最后的颜色或者模型上每一个像素。光照模型是着色器真正计算发生的地方——它正是决定光打在材料上效果的代码。

这章的内容:

>Diffuse shading

>Using packed arrays

>Adding a texture to a shader

>Scrolling textures by modifying UV values

>Normal mapping

>Creating a transparent material

>Creating a Holographic Shader

>Packing and blending textures

>Creating a circle around your terrain


Diffuse shading

漫反射着色

Diffuse,即漫反射,对象可能会有一个统一的颜色和光滑的表面,但是还不足以光滑到镜面一样反射光。这样不光滑的材料最好使用漫反射着色器。虽然在真实世界里完全的漫反射材质不存在;在审美要求不高的大型应用中使用漫反射着色器是相对廉价实用的。

一个基本的漫反射光照模型,需要包括以下几个部分:一个反射颜色(emissive color,可以理解成平面本身的颜色), 一个环境光颜色(ambient color,可以理解成光源的颜色), 以及计算来自所有光源的光线和。

步骤

  1. 删除所有属性,除了_Color
  2. 在SubShader{}中,删除_MainText, _Glossiness 和 _Metallic变量,uv_MainTex不能删除,因为Cg不允许Input结构为空
  3. 删除surf()中的内容以o.Albedo = _Color.rgb;代替

代码

Shader “chapter2/StandardDiffuse” {

    // We define Properties in the properties block

    Properties {

        _Color (“Color”Color) = (1,1,1,1)

    }

    SubShader {

        Tags { “RenderType”=“Opaque” }

        LOD 200

        CGPROGRAM

        #pragma surface surf Standard fullforwardshadows

        #pragma target 3.0

        struct Input {

            float2 uv_MainTex;

        };

        fixed4 _Color;

        void surf (Input IN, inout SurfaceOutputStandard o) {

            o.Albedo = _Color.rgb;

        }

        ENDCG

    }

    FallBack “Diffuse”

}

解释

  1. 因为这个shader是从Standard Shader修改的,它会使用physically-based rendering。如果想要尝试一个非真实感效果(non-photorealistic look),可以把第一个#pragma的Standard改成Lambert,把SurfaceOutputStandard改成SurfaceOutput
  2. 着色器允许通过一个表面输出(surface output)来将材质上的渲染属性(rendering properties)和他们的光照模型(lighting model)通讯。这个输出实际上就是一个当前光照模型所需要的参数的封装。不同的光照模型需要不同的输出结构体就不奇怪了。
  3. Diffuse Shader 对应 SurfaceOutputStandard
  4. Specular Shader 对应 SurfaceOutputStandardSpecular
  5. 正确使用表面着色器就是用正确的值初始化表面输出。

表面输出

属性

SurfaceOutput

fixed3 Albedo; 材质的漫反射颜色

fixed3 Normal; 切线空间法线?

fixed3 Emission;材料本身光的颜色

fixed Alpha; 材质透明度

half Specular; 高光强度 0 到1 specular power?

fixed Gloss; 光泽 specular intensity?

SurfaceOutputStandard

fixed3 Albedo; 材质的基础颜色

fixed3 Normal;

haf3 Emission;这里被声明为half3

fixed Alpha;

half Occlusion; 环境光屏蔽?

half Smoothnewss; 0 = rough, 1 = smooth

half Metallic; 0 = non-metal, 1 = metal

SurfaceOutputStandard

fixed3 Albedo;

fixed3 Normal;

haf3 Emission;

fixed Alpha;

half Occlusion;

half Smoothnewss;

half Metallic;

fixed3 Specular; 高光颜色


Using packed arrays

使用合并数组

在Cg中有两种类型的变量,single values和packed arrays后面这种的类型以数字结尾,如float3或者int4,但是他们和传统意义的数组并不一样。合并数组的元素可以通过xyzw访问,或者rgba访问,实际上用x和r是一样的,但是对读代码的人可以有很大意义上的不同。

有一种c#中截然不同的用法: swizzling调和 允许用一行访问和重排合并数组的元素。如 o.Albedo = _Color.rgb; Albedo是一个fixed3而_Color被定义为fixed4

如果一个单值被赋于一个合并数组,会拷贝到其所有字段:smearing涂抹

o.Albedo = 0// black(0,0,0)

o.Albedo = 1// white(1,1,1)

调和也能用到左边的表达式,被称作masking遮盖

o.Albedo.rg = _Color.rg;

Packed matrices

Cg允许像float4x4的类型,表示为4×4的浮点数矩阵,可以访问某一元素通过_mRC标记,R是行,C是列:

float4x4 matrix;

float first = matrix._m00;

float last = matrix._m33;

也可以链接使用

float4 diagonal = matrix._m00_m11_m22_m33;

用方括号访问一行

float4 firstRow = matrix[0];

合并数组是Cg最优雅的特性之一,可以在Cg的网站查看更多


Adding a texture to a shader

给一个着色器添加纹理

Texture mapping材质贴图是一个2D图片贴图到3D模型的过程。Models模型是由三角形组成的,所有的顶点都有UVdata,保存UV两个坐标,从01,他们表示了2D图片的像素的坐标。当三角形中间的像素要被贴图的时候,GPU会通过插值接近的UV值来找到正确的纹理的像素来用。如图:

UV数据被存在3D模型中,需要建模软件编辑。

步骤

  1. 创建一个Standard Shader 命名为TexturedShader
  2. 创建一个新的材料命名为TexturedMateri
  3. 把这个shader拖到material上
  4. 把纹理拖到Albedo

解释

  1. 标准着色器的材质贴图是对开发者完全透明的。想要理解它是怎么运作的,需要仔细看TexturedShader的属性:_MainTex (“Albedo (RGB)”, 2D) = “white” {}

  2. 在CGPROGRAM部分,这个材质被定义为标准2D材质类: sampler2D _MainTex;

  3. 下一行Input结构体中有一个合并属性:struct Input { float2 uv_MainTex;}
  4. 每一次surf() surface function被调用,Input结构体会包含特定3D模型上某一个点的_MainTex的UV信息。标准着色器会自动识别将uv_MainTex对应_MainTex并初始化。UV如何从3D转化到2D会在下一章谈论

  5. 最后UV信息被用来取样:fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; tex2D用来从纹理上特定UV返回该位置的像素
  6. Texture纹理可以设置Filter 模式,决定了当该纹理被取样的时候如何插值,Bilinaer双线性插值大部分时候是有效且廉价的,但是对于一些二维游戏,还是用Point来移除插值比较好.

  7. 当纹理作用于台阶或者天花板那样陡峭的角度,可以提高Aniso Level

更多关于纹理贴图,造访Cg教程


Scrolling textures by modifying UV values

修改UV值滚动纹理

滚动贴图可以创造出如瀑布,流动的岩浆,河流的效果。这个技巧也是制作动画精灵的基础。

步骤

  1. 修改属性,加入滚动速度的属性
  2. 修改surf(),用内置的_Time变量来使得UV在游戏开始后随着时间变化

代码

Shader “Chapter2/ScrollingShader” {

    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

    }

    SubShader {

        Tags { “RenderType”=“Opaque” }

        LOD 200

        CGPROGRAM

        #pragma surface surf Standard fullforwardshadows

        #pragma target 3.0

        struct Input {

            float2 uv_MainTex;

        };

        fixed4 _MainTint;

           fixed _ScrollXSpeed;

           fixed _ScrollYSpeed;

           sampler2D _MainTex;

        void surf (Input IN, inout SurfaceOutputStandard o) {

            // Create a separate variable to store our UVs

            // before we pass them to the tex2D() function

              fixed2 scrolledUV = IN.uv_MainTex;

              // Create variables that store the individual x and y

              // components for the UV’s scaled by time

              fixed xScrollValue = _ScrollXSpeed * _Time;

              fixed yScrollValue = _ScrollYSpeed * _Time;

              // Apply the final UV offset

              scrolledUV += fixed2(xScrollValue, yScrollValue);

              // Apply textures and tint

              half4 c = tex2D (_MainTex, scrolledUV);

              o.Albedo = c.rgb * _MainTint;

              o.Alpha = c.a;

        }

        ENDCG

    }

    FallBack “Diffuse”

}

解释

  1. _Time是一个float4类型内置变量这里可以查到内置类型的相关内容Time since level load (t/20, t, t*2, t*3), use to animate things inside the shaders.

Normal mapping

法线贴图

每一个3D模型的三角形都有一个方向,不同方向的三角形反射光的方向也不同,对于弯曲的物体这就可能是个问题了因为显然物体的集合是由平的三角形组成。

为了解决这个问题,在光反射的时候,用顶点的法向量取代三角形的方向,法向也是除了UVdata外最常用的信息,在三角形中的每一个点都有自己的法向,是通过线性插值其周围的顶点法向量得到的。所以即使几何相同,通过在表面法向量插值,可以让表面变粗糙。

这样计算法向的技巧被one-normal贴图代替。这种新的技巧使用额外的纹理用其RGB值来表示法向量

Some applications such as CrazyBump and NDO Painter will take in 2D data and convert it to normal data for you. Other applications such as Zbrush 4R7 and AUTODESK will take 3D sculpted data and create normal maps for you.

代码

Shader “chapter2/NormalMappingShader” {

    Properties {

        _Color (“Color”Color) = (1,1,1,1)

        _MainTex (“Albedo (RGB)”2D) = “white” {}

        _BumpTex(“Normal map”2D) = “bump” {}

        _BumpIntensity(“Normal intensity”Range(0,1)) = 1

        _Glossiness (“Smoothness”Range(0,1)) = 0.5

        _Metallic (“Metallic”Range(0,1)) = 0.0

    }

    SubShader {

        Tags { “RenderType”=“Opaque” }

        LOD 200

        CGPROGRAM

        // Physically based Standard lighting model, and enable shadows on all light types

        #pragma surface surf Standard fullforwardshadows

        // Use shader model 3.0 target, to get nicer looking lighting

        #pragma target 3.0

        sampler2D _MainTex;

        sampler2D _BumpTex;

        fixed _BumpIntensity;

        struct Input {

            float2 uv_MainTex;

        };

        half _Glossiness;

        half _Metallic;

        fixed4 _Color;

        void surf (Input IN, inout SurfaceOutputStandard o) {

            // Albedo comes from a texture tinted by color

            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;

            o.Albedo = c.rgb;

            fixed4 n = tex2D(_BumpTex, IN.uv_MainTex) * _BumpIntensity;

            o.Normal = UnpackNormal(n).rgb;

            // Metallic and smoothness come from slider variables

            o.Metallic = _Metallic;

            o.Smoothness = _Glossiness;

            o.Alpha = c.a;

        }

        ENDCG

    }

    FallBack “Diffuse”

}

效果

下面是一个提供的法线贴图前后对比的例子,可以看到光照更多的细节,并且在不同的角度会有区别

这是书上的例子,可以更直观看到法线贴图的效果

咋回事儿

  1. 这里UnpackNormal()函数把RGB01的值转换到向量-1+1的值,Unity帮你处理了法线贴图并提供正确的数据类型。
  2. UnpackNormal处理完法线贴图以后,就把它送给SurfaceOutput结构图,通过o.Normal = normalMap.rgb
  3. 看来其中具体的原理这本书没有囊括,可能要之后研究一下
  4. unpacked的法线贴图乘一个系数可以调整其强度

fixed4 n = tex2D(_BumpTex, IN.uv_MainTex) * _BumpIntensity;

o.Normal = UnpackNormal(n).rgb;


Creating a transparent material

创建一个透明的材料

透明材质可以用作火焰效果、窗户玻璃。在渲染固体模型的时候,Unity将他们按z排序,即他们离摄像机的距离,渲染将跳过哪些朝向背离摄像机的三角形(culling剔除),所以在渲染透明几何体的时候,两个需要渲染的面就可能会造成问题。接下来会讨论一些透明表面着色器需要解决的问题,这个话题以后第6章还会重新讨论。

步骤

  1. 创建TransparentShader,一个新的材料,将其设置在一个quad或者plane对象上
  2. 制作一个透明玻璃的图片,它的alpha channel会被利用来决定玻璃的透明度
  3. 打开Photoshop,为图片创建一个白色的蒙板mask
  4. 选中图片上希望称为半透明的部分填充蒙板为更深的颜色
  5. 代码中SubShader{}的Tags中标记着色器是透明的
  6. 因为这个着色器是为2维材料准备的,所以模几何体模型背面要确保不被画出来,用Cull Back
  7. 告诉着色器材料是透明的,需要和屏幕前画好的东西混合在一起#pragma surface surf Standard alpha:fade
  1. 用这个着色器来决定玻璃的颜色和透明度

代码

Shader “Chapter2/TransparentShader” {

    Properties {

        _Color (“Color”Color) = (1,1,1,1)

        _MainTex (“Albedo (RGB)”2D) = “white” {}

    }

    SubShader {

        // 标记透明 signal the shader is transparent

        Tags { 

            “Queue” = “Transparent”

            “IgnoreProjector” = “True”

            “RenderType” = “Transparent”

        }

        //LOD 200

        // Removes back geometry

        //Cull Off

        CGPROGRAM

        #pragma surface surf Standard alpha:fade

        #pragma target 3.0

        sampler2D _MainTex;

        struct Input {

            float2 uv_MainTex;

        };

        fixed4 _Color;

        void surf (Input IN, inout SurfaceOutputStandard o) {

            float4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color; 

            o.Albedo = c.rgb;

            o.Alpha = c.a;

        }

        ENDCG

    }

    FallBack “Diffuse”

}

效果

在PS里最终的图层和通道情况

在Unity里预览点选通道切换可以看到创建模版时多的alpha通道

可以透过窗户看到窗户后面的士兵

所以说

  1. Tags是用来添加对象如何被渲染的信息的
  2. “Queue” = “Transparent” 用来给对象排序,默认情况下,根据对象离相机的距离近的覆盖远的对象。有些内置的队列可以让我们写着色器和操作实时渲染更方便:Background,Geometry,AlphaTest,Transparent,Overlay
  3. Render queue

    Render queue description

    render queue value

    Background

    第一个渲染,用于skyboxes之类的

    1000

    Geometry

    默认渲染队列,用于不透明的几何体

    2000

    AlphaTest

    2450

    Transparent

    3000

    Overlay

    4000

  4. 透明队列在几何之后渲染并不意味着玻璃会出现在其他固体对象上面,而是Unity会最后画玻璃而不会渲染出现在玻璃后面东西。这是通过ZBuffering实现的
  5. IgnoreProjector让对象不被Unity的projectors影响,这是什么?
  6. RenderType在shader replacement着色器替换中有作用,暂时不讨论

Creating a Holographic Shader

创建一个全息着色器

全系影像在很多太空主题游戏中出现过,它们总是物体半透明的投影。一个简单的全系投影可以从添加噪声,动画扫描西线,震动来创造逼真的全息效果。因为全息效果只显示物体的轮廓,我们称这个着色器焦作Slihouette.

步骤

  1. 添加新的属性_DotProduct
  2. 透明的话,就把一些Tag加上
  3. #pragma surface surf Standard fullforwardshadows 改成#pragma surface surf Lambert alpha:fade nolighting
  4. 修改Input结构,增加当前视图方向和世界法线方向?
  5. 修改输出类型为StandardOutout
  6. 计算透明度

float border = 1 – (abs(dot(IN.viewDir, IN.worldNormal)));

float alpha = (border * (1 – _DotProduct) + _DotProduct);

o.Alpha = c.a * alpha;

代码

Shader “Chpater2/Slihouette” {

    Properties {

        _Color (“Color”Color) = (1,1,1,1)

        _MainTex (“Albedo (RGB)”2D) = “white” {}

        _Glossiness (“Smoothness”Range(0,1)) = 0.5

        _Metallic (“Metallic”Range(0,1)) = 0.0

        _DotProduct(“Rim effect”Range(-1,1)) = 0.25

    }

    SubShader {

        Tags { 

            “Queue” = “Transparent” 

            “IgnoreProjector” = “True” 

            “RenderType” = “Transparent”

         }

        // Cull Off

        // so that the back of the model won’t be removed (culled)

        CGPROGRAM

        // This shader is not trying to simulate a realistic material, 

        // so there is no need to use the PBR lighting model. 

        // The Lambertian re ectance, which is very cheap, is used instead. 

        // Additionally, we should disable any lighting with nolighting 

        // and signal to Cg that this is a Transparent Shader using alpha:fade:

        #pragma surface surf Lambert alpha:fade nolighting

        #pragma target 3.0

        sampler2D _MainTex;

        //

        struct Input {

            float2 uv_MainTex; 

            float3 worldNormal; 

            float3 viewDir;

        };

        half _Glossiness;

        half _Metallic;

        fixed4 _Color;

        float _DotProduct;

        // this shader is using the Lambertian reflectance as its lighting function, 

        // the name of the surface output structure should be changed accordingly to 

        // SurfaeOutput instead of SurfaceOutputStandard:

        void surf (Input IN, inout SurfaeOutput o) {

            float4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb;

            float border = 1 – (abs(dot(IN.viewDir, IN.worldNormal)));

            float alpha = (border * (1 – _DotProduct) + _DotProduct);

             o.Alpha = c.a * alpha;

        }

        ENDCG

    }

    FallBack “Diffuse”

}

截图

解释

  1. 这个shader只显示物体的silhouette (轮廓),当我们改变观察的方向,它的轮廓也会随之改变。几何上说,所有的轮廓应当是那些当前观察视角和法线向量成90度的三角形,所以在Input中我们分别声明了wordNormal和viewDir
  2. 点积函数dot(IN.viewDir, IN.worldNormal) 返回0如果两个向量是orthogonal(成90度
  3. 我们用_DotProduct属性来决定,一个三角形的点积与0有多的时候会完全消失。
  4. 我们希望点积为0的时候完全可见,点积为1的时候透明float border = 1 – (abs(dot(IN.viewDir, IN.worldNormal))); float alpha = (border * (1 – _DotProduct) + _DotProduct);

更多

这个技巧简单便宜,但是用途很多

  1. 科幻游戏中星球有点颜色的大气层
  2. 显示被选中物体的边缘
  3. 幽灵
  4. 爆炸冲击波
  5. 飞船的防护罩

 


Packing and blending textures

打包和混合纹理

纹理(Textures)不仅存储信息好用,不只是我们会正常想到的像素颜色,而且也好用于存储多组的在x和y方向上的像素和RGBA通道。 我们确实可以把多张图片打包成一个RGBA纹理,并在shader代码里分别利用RGBA组成部分自己作为单独的纹理。

纹理占了应用所占空间的很大一部分,所以为了减少应用的体积,可以着手将多个纹理合并成一个纹理。

一个使用打包纹理的例子是当需要把多个纹理施加到一个表面,地表类型的着色器就可能是这样的。

步骤

  1. 准备4张要搅和在一起的纹理,一个棒棒的土地着色器应该需要草、泥土、有石头的泥土、石头纹理吧。
  1. 在Create/3D Object/Terrain创建地形,并创作一番
  1. 我们需要5个sampler2D对象(RGBA4个通道纹理以及最终的混合纹理)和两个颜色属性
  2. 完成如下代码
  3. 完成material赋给地形

代码

Shader “Chapter2/PackingAndBlending” {

    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 {

        Tags { “RenderType”=“Opaque” }

        CGPROGRAM

        #pragma surface surf Lambert

        #pragma target 4.0

        // 这样我们就可以使用各个纹理的uv信息了

        struct Input {

            float2 uv_RTexture; 

            float2 uv_GTexture; 

            float2 uv_BTexture;

            float2 uv_ATexture;

            float2 uv_BlendTex;

        };

        // 创建八个变量,和属性建立链接精度够用就好,颜色和单位向量使用fixed

        // 为了根据每一张不同的texture来改变其在地形上的tiling rates

        //(平铺率,可以理解为地上某些区域草比较密集,某些地区石头比较多等)        

        float4 _MainTint; 

        float4 _ColorA; 

        float4 _ColorB; 

        sampler2D _RTexture; 

        sampler2D _GTexture; 

        sampler2D _BTexture; 

        sampler2D _ATexture;

        sampler2D _BlendTex; 

        void surf (Input IN, inout SurfaceOutput o) {

            //存储各个纹理的RGBA信息

            //Get the pixel data from the blend texture

            //we need a float 4 here because the texture

            //will return R,G,B,and A or X,Y,Z, and W

            float4 blendData = tex2D(_BlendTex, IN.uv_BlendTex);

            //Get the data from the textures we want to blend 

            float4 rTexData = tex2D(_RTexture, IN.uv_RTexture); 

            float4 gTexData = tex2D(_GTexture, IN.uv_GTexture); 

            float4 bTexData = tex2D(_BTexture, IN.uv_BTexture); 

            float4 aTexData = tex2D(_ATexture, IN.uv_ATexture);

            //用lerp函数将两种纹理合成

            //Now we need to contruct a new RGBA value and add all 

            //the different blended texture back together

            float4 finalColor;

            finalColor = lerp(rTexData, gTexData, blendData.g); 

            finalColor = lerp(finalColor, bTexData, blendData.b); 

            finalColor = lerp(finalColor, aTexData, blendData.a);

            finalColor.a = 1.0;

            //用红色通道决定tint

            //Add on our terrain tinting colors

            float4 terrainLayers = lerp(_ColorA, _ColorB, blendData.r); 

            finalColor *= terrainLayers;

            finalColor = saturate(finalColor);

            o.Albedo = finalColor.rgb * _MainTint.rgb;

            o.Alpha = finalColor.a;

        }

        ENDCG

    }

    FallBack “Diffuse”

}

纹理

发现书中的四种泥土草石头图片在书现在版本的资源里已经找不到了,所以还是找了上一个版本的资源才找到。可以看到还有很多组合可以选择。

效果

或者

解释

  1. 为什么要这样合并纹理呢?在我们的应用中,textures的数目将很大程度上影响应用的性能。因此,为了减少textures的数量,我们可以看看Shader中使用的那些图片可以合并成一张,以此来优化性能
  2. 第五个纹理图是干什么用的呢?其实它由多个灰度图组成存储着上述四种纹理在目标地形上是如何分布的,这样做每个像素点的分布比例都可能有所不同。
  3. 上面的代码有点多其实可以先学习一下lerp()这个内置函数
  4. lerp(value : a, value : b, blend : c)

    (1-f)*a + b*f

    这里的话a和b是两个向量,而f参数可以是一个数值或者同类型的向量

  5. 例如,我们想要在1和2之间找到一个中间值,我们可以使用0.5作为第三个参数,那么它将会返回1.5。因为一张texture的四个通道RGBA值都是简单的float类型,取值范围在0到1,因此可以使用它们作为混合程度值,即lerp的第三个参数来完成我们混合texture的需要。在Shader中,我们仅从blend texture的四个通道中选择一个来控制每个像素颜色的混合结果。例如,我们从grass texture和dirt texture中提取颜色值,并使用blend texture对应的G通道值进行lerp运算。如果可视化上述计算,可以参见下图:
  6. Shader可以如此简单地使用blend texture的四个通道值,以及其他用于颜色的texture(如grass texture等),来创建出最后的混合而得的texture。这个最后的texture成为我们最终的地形颜色,并会和diffuse light(上述代码中的_MainTint变量)进行乘法运算,来得到最终效果。颜色值_ColorA_ColorB的用途是什么。从代码里可以看出来我们使用了blend texture的R通道值用于混合这两个颜色值,并和之前4张texture的混合结果相乘,这两个颜色值混合的结果可以看成是该地形本身的颜色,例如有红土地、黄土地、黑土地之类的区别。
  7. 这样少少几张图片就可以给很大的地图提供材料啦。

纠错

书中给的代码如果不加#pragma target 4.0 就会报错:

Too many texture interpolators would be used for ForwardBase pass (11 out of max 10)。目前还没有找到其他解决办法


Creating a circle around your terrain

在你的地形上画一个圈儿

很多的游戏会在选中的目标周围画一个圈儿,这个圈儿用来指示比如攻击范围、移动范围、视野等在内的指示距离。如果地形是平的,那么用一个quad就能完成任务,但如果不是这样简单的情况,像有山坡这样的,quad就不能胜任了。配合c#代码,我们可以使用shader来往地形上画一个跟随物体的圈

开始

  1. 创建新的着色器和材料RadiusShaderRadiusMaterial
  2. 上一节里面的地形准备好,如果没有,重新通过Create/3D object/ Terrain创建,然后使用内置的工具为地形画它的几何形状。
  3. 在Unity中地形是一种特殊的对象,他们的纹理贴图和传统3D模型不同。我们无法通过着色题提供_MainTex而必须直接从地形本身提供。所以我们如上图选中Paint Texture/EditTextures然后Add Texture添加纹理。
  1. 现在纹理已经选中,仍然在地形的Inspector中的Terrain Settings里改变Material为Custom然后为其选中RadiusMateiral

代码

Shader “Chapter2/RadiusShader” {

    Properties {

        _Color (“Color”Color) = (1,1,1,1)

        _MainTex (“Albedo (RGB)”2D) = “white” {}

        _Glossiness (“Smoothness”Range(0,1)) = 0.5

        _Metallic (“Metallic”Range(0,1)) = 0.0

        // (1)添加4个新的属性

        _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

    }

    SubShader {

        Tags { “RenderType”=“Opaque” }

        LOD 200

        CGPROGRAM

        // Physically based Standard lighting model, and enable shadows on all light types

        #pragma surface surf Standard fullforwardshadows

        // Use shader model 3.0 target, to get nicer looking lighting

        #pragma target 3.0

        sampler2D _MainTex;

        // (3)为了实现效果我们还需要每个像素点的世界坐标

        struct Input {

            float2 uv_MainTex;

            float3 worldPos;

        };

        half _Glossiness;

        half _Metallic;

        fixed4 _Color;

        // (2)为新的属性建立链接

        float3 _Center;

        float _Radius;

        fixed4 _RadiusColor;

        float _RadiusWidth;

        void surf (Input IN, inout SurfaceOutputStandard o) {

            // (4)像素点和圆心的距离,如果在园环内就赋上自选的颜色

            float d = distance(_Center, IN.worldPos);

            if (d > _Radius && d < _Radius + _RadiusWidth)

                   o.Albedo = _RadiusColor;

             else

                   o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;

        }

        ENDCG

    }

    FallBack “Diffuse”

}

截图

移动

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class MoveRadius : MonoBehaviour {

    public Material radiusMaterial;

    public float radius = 1;

    public Color color = Color.white;

    // Update is called once per frame

    void Update () {

        radiusMaterial.SetVector (“_Center”, transform.position);

        radiusMaterial.SetFloat (“_Radius”, radius);

        radiusMaterial.SetColor (“_RadiusColor”, color);

    }

}

把代码赋给我们的角色,设置好Radius Mateiral,开始游戏,移动角色,就会有圆圈出现在角色的周围并跟随着在地形表面移动啦。

解释

  1. 通过给Input结构体添加worldPos变量,我们请求Unity给我们提供了每个像素的坐标,和编辑器里提供的对象坐标是一致的。
  2. surf()函数中使用了内置函数distance计算了像素点和中心的距离

「Unity Shaders」Creating First Shader

创建第一个着色器 最近学习了一段时间Unity Shader相关知识,在进一步自顶向下学习计算机图形学之前,先将之前看《Unity 5.x Shaders and Effects Cookbook...

阅读全文

《Effective c++》、《Inside C++ Model》 小结(一)

最近瞻仰了一下Scott Meyers久负盛名的《effective c++》,特来总结一下,以加深一下印象与防止自己今后记忆力衰退。这本书里很多都是工程上很有意思的tips...

阅读全文

网易互娱2017实习生招聘在线笔试第一场

本周五参加了网易互娱2017实习生招聘在线笔试第一场(难倒还有好几场),题目总体上比较基础,一读题就有思路,不过手速快才能写完。 比赛链接 题目1 : ...

阅读全文

1 条评论

欢迎留言