本系列主要参考《Unity Shaders and Effects
Cookbook》
一书(感谢原书作者),同时会加上一点个人理解或拓展。

这里是本书所有的插图。这里是本书所需的代码和资源(当然你也可以从官网下载)。

========================================== 分割线 ==========================================

题外话


好久没有更新博客!感觉又荒废了shader的学习,吼吼,还是得加油啊!

这次的内容是打包合并textures。

Textures不仅仅可以用来存储颜色信息,还可以存储很多数据信息。这些数据信息可以分别存储到R、G、B、A四个部分,然后再打包成一张Texture,像下图这样:

为什么这样做有好处呢?在我们的应用中,textures的数目将很大程度上影响应用的性能。因此,为了减少textures的数量,我们可以看看Shader中使用的那些图片可以合并成一张,以此来优化性能。

任何灰度图都可以被打包进另一个新的texture的RGBA四个中的某一个chanel。这听起来不是很明白,没关系,这篇文章就会展示如何做到这点。

一个常用的场景是,你想要混合多张textures到一个surface上。这在terrain Shaders(地形渲染)中很常见,这种时候你往往需要很好的将一张texture和另一张混合起来。

这篇文章中将会告诉你,怎样完成一个由4张textures混合渲染而得的terrain Shader。

开始工作


  1. 还是创建一个新的Shader文件,并为这个Shader创建一个新的Material,名字可以称为TextureBlending;
  2. 创建一个新的场景,以便来测试我们的Shader;
  3. 接下来,你需要4张用于混合的textures。它们可以是任何图片,但是为了得到一个效果较好的terrain Shader,建议你分别准备一张草地(grass)、泥土(dirt)、小石子(rocky dirt)、石头(rock)的texture。书的资源中包含了这样四张texture(Unity
    Shaders and Effects Cookbook/5084_Code/Unity assets/5084_02_UnityAssets/Textures目录下的Chapter02_Grass0103_2_S.jpg,Chapter02_RockSmooth0045_2_S.jpg,Chapter02_SandPebbles0027_1_S.jpg,Chapter02_SandPebbles0030_3_S.jpg):

  4. 最后,也是奇妙所在,我们需要一张混合的texture(Unity Shaders and Effects Cookbook/5084_Code/Unity assets/5084_02_UnityAssets/Textures目录下的Chapter02_TerrainBlend_001.jpg),它是由多个灰度图混合而成的。它将会告诉我们以上四张texture在目标地形上是如何分布的:



实现


  1. 首先,向Shader的Properties块中添加一些新的properties。我们需要5个sampler2D对象,也就是textures,以及两个颜色properties,和一个用于调整整体地形颜色的值。

    1. Properties {
    2. _MainTint ("Diffuse Tint", Color) = (1,1,1,1)
    3.  
    4. //Add the properties below so we can input all of our textures
    5. _ColorA ("Terrain Color A", Color) = (1,1,1,1)
    6. _ColorB ("Terrain Color B", Color) = (1,1,1,1)
    7. _RTexture ("Red Channel Texture", 2D) = ""{}
    8. _GTexture ("Green Channel Texture", 2D) = ""{}
    9. _BTexture ("Blue Channel Texture", 2D) = ""{}
    10. _ATexture ("Alpha Channel Texture", 2D) = ""{}
    11. _BlendTex ("Blend Texture", 2D) = ""{}
    12. }
  2. 在SubShader中创建8个变量,分别对应Properties中的8个properties,以建立和它们之间的链接。
    1. CGPROGRAM
    2. #pragma surface surf Lambert
    3.  
    4. float4 _MainTint;
    5. float4 _ColorA;
    6. float4 _ColorB;
    7. sampler2D _RTexture;
    8. sampler2D _GTexture;
    9. sampler2D _BTexture;
    10. sampler2D _BlendTex;
    11. sampler2D _ATexture;
  3. 为了根据每一张不同的texture来改变其在地形上的tiling rates(平铺率,可以理解为地上某些区域草比较密集,某些地区石头比较多等),我们需要修改结构体。
    1. struct Input {
    2. float2 uv_RTexture;
    3. float2 uv_GTexture;
    4. float2 uv_BTexture;
    5. float2 uv_ATexture;
    6. float2 uv_BlendTex;
    7. };
  4. 在surf函数中,得到每张texture的信息,并分别存储在它们对应的变量中。
    1. //Get the pixel data from the blend texture
    2. //we need a float 4 here because the texture
    3. //will return R,G,B,and A or X,Y,Z, and W
    4. float4 blendData = tex2D(_BlendTex, IN.uv_BlendTex);
    5.  
    6. //Get the data from the textures we want to blend
    7. float4 rTexData = tex2D(_RTexture, IN.uv_RTexture);
    8. float4 gTexData = tex2D(_GTexture, IN.uv_GTexture);
    9. float4 bTexData = tex2D(_BTexture, IN.uv_BTexture);
    10. float4 aTexData = tex2D(_ATexture, IN.uv_ATexture);
  5. 使用lerp函数将四张texture混合。lerp函数有三个参数,lerp(value
    : a, value : b, blend : c)。它从前两个参数中得到数据,并使用最后一个参数混合前两个值。

    1. //No we need to contruct a new RGBA value and add all
    2. //the different blended texture back together
    3. float4 finalColor;
    4. finalColor = lerp(rTexData, gTexData, blendData.g);
    5. finalColor = lerp(finalColor, bTexData, blendData.b);
    6. finalColor = lerp(finalColor, aTexData, blendData.a);
    7. finalColor.a = 1.0;
  6. 最后,我们使用blending texture的R通道值混合两个颜色色调值,并将结果与之前的混合值相乘。
    1. //Add on our terrain tinting colors
    2. float4 terrainLayers = lerp(_ColorA, _ColorB, blendData.r);
    3. finalColor *= terrainLayers;
    4. finalColor = saturate(finalColor);
    5.  
    6. o.Albedo = finalColor.rgb * _MainTint.rgb;
    7. o.Alpha = finalColor.a;

整体代码如下:
  1. Shader "Custom/TextureBlending" {
  2. Properties {
  3. _MainTint ("Diffuse Tint", Color) = (1,1,1,1)
  4.  
  5. //Add the properties below so we can input all of our textures
  6. _ColorA ("Terrain Color A", Color) = (1,1,1,1)
  7. _ColorB ("Terrain Color B", Color) = (1,1,1,1)
  8. _RTexture ("Red Channel Texture", 2D) = ""{}
  9. _GTexture ("Green Channel Texture", 2D) = ""{}
  10. _BTexture ("Blue Channel Texture", 2D) = ""{}
  11. _ATexture ("Alpha Channel Texture", 2D) = ""{}
  12. _BlendTex ("Blend Texture", 2D) = ""{}
  13. }
  14. SubShader {
  15. Tags { "RenderType"="Opaque" }
  16. LOD 200
  17.  
  18. CGPROGRAM
  19. #pragma surface surf Lambert
  20.  
  21. float4 _MainTint;
  22. float4 _ColorA;
  23. float4 _ColorB;
  24. sampler2D _RTexture;
  25. sampler2D _GTexture;
  26. sampler2D _BTexture;
  27. sampler2D _BlendTex;
  28. sampler2D _ATexture;
  29.  
  30. struct Input {
  31. float2 uv_RTexture;
  32. float2 uv_GTexture;
  33. float2 uv_BTexture;
  34. float2 uv_ATexture;
  35. float2 uv_BlendTex;
  36. };
  37.  
  38. void surf (Input IN, inout SurfaceOutput o) {
  39. //Get the pixel data from the blend texture
  40. //we need a float 4 here because the texture
  41. //will return R,G,B,and A or X,Y,Z, and W
  42. float4 blendData = tex2D(_BlendTex, IN.uv_BlendTex);
  43.  
  44. //Get the data from the textures we want to blend
  45. float4 rTexData = tex2D(_RTexture, IN.uv_RTexture);
  46. float4 gTexData = tex2D(_GTexture, IN.uv_GTexture);
  47. float4 bTexData = tex2D(_BTexture, IN.uv_BTexture);
  48. float4 aTexData = tex2D(_ATexture, IN.uv_ATexture);
  49.  
  50. //No we need to contruct a new RGBA value and add all
  51. //the different blended texture back together
  52. float4 finalColor;
  53. finalColor = lerp(rTexData, gTexData, blendData.g);
  54. finalColor = lerp(finalColor, bTexData, blendData.b);
  55. finalColor = lerp(finalColor, aTexData, blendData.a);
  56. finalColor.a = 1.0;
  57.  
  58. //Add on our terrain tinting colors
  59. float4 terrainLayers = lerp(_ColorA, _ColorB, blendData.r);
  60. finalColor *= terrainLayers;
  61. finalColor = saturate(finalColor);
  62.  
  63. o.Albedo = finalColor.rgb * _MainTint.rgb;
  64. o.Alpha = finalColor.a;
  65. }
  66. ENDCG
  67. }
  68. FallBack "Diffuse"
  69. }

我们新建一个地形,并把创建的Material赋给它后,可以看到以下效果:




解释


上面的代码看起来有点复杂,但实际上混合的实质非常简单。为了完成以上效果,我们使用了CGFX标准库中内置的lerp函数。该函数允许我们从参数一合参数二之间挑选一个值,并使用参数三作为混合程度。它的工作原理如下所示:

例如,我们想要在1和2之间找到一个中间值,我们可以使用0.5作为第三个参数,那么它将会返回1.5。因为一张texture的四个通道RGBA值都是简单的float类型,取值范围在-到1,因此可以使用它们作为混合程度值,即lerp的第三个参数来完成我们混合texture的需要。

在Shader中,我们仅从blend texture的四个通道中选择一个来控制每个像素颜色的混合结果。例如,我们从grass texture和dirt texture中提取颜色值,并使用blend texture对应的G通道值进行lerp运算。

如果可视化上述计算,可以参见下图:

Shader可以如此简单地使用blend texture的四个通道值,以及其他用于颜色的texture(如grass texture等),来创建出最后的混合而得的texture。这个最后的texture成为我们最终的地形颜色,并会和diffuse light(上述代码中的_MainTint变量)进行乘法运算,来得到最终效果。

细心的话,你可能会好奇上述代码中的两个颜色值_ColorA和_ColorB的用途是什么。从代码里可以看出来我们使用了blend texture的R通道值用于混合这两个颜色值,并和之前4张texture的混合结果相乘,这两个颜色值混合的结果可以看成是该地形本身的颜色,例如有红土地、黄土地、黑土地之类的区别。

扩展——灰度图


最后,讲一下灰度图的原理,如果你对灰度图十分了解,那么就可以关闭这个网页了。


当你把四张Textures按不同的顺序对RGBA赋值的话,就会得到不同的效果。

例如,当Material的Inspector按照左边这样赋值后,会得到右面的地形效果。


如果按照另一个顺序赋值,则会得到不同的效果。


这里面的原因当然是因为在blend texture中不同的通道值不一样。我们的blend texture如下所示:


正如灰度图的名字所示,灰度图中没有彩色只有灰色,即介于白色和黑色之间的颜色。那么RGBA四个通道的灰度图是如何得到最右边的彩色图的呢?
简单说来,这四个通道分别代表了红、绿、蓝、和透明度四种颜色值的浓度。我们以R通道的灰度图,即最左边的灰度图为例。
在R通道的灰度图中,越亮的部分,即颜色越白的部分,表示红色光在此处越亮,亮度级别越接近1(亮度范围在这里为0到1),表现为颜色越红,这可以从最右边的彩色图看出来(当然还会和蓝绿进行颜色混合得到最后彩色效果)。
相反,越黑的地方表示红色越弱,亮度级别越接近0。

为了方便记忆,我们可以记住下面四条原理:
  1. 通道中的纯白,代表了该色光在此处为最高亮度,亮度级别是1。
  2. 通道中的纯黑,代表了该色光在此处完全不发光,亮度级别是0。
  3. 介于纯黑纯白之间的灰度,代表了不同的发光程度,亮度级别介于0至1之间。
  4. 灰度中越偏白的部分,表示色光亮度值越高,越偏黑的部分则表示亮度值越低。

用到我们的例子中,在进行lerp(value : a,
value : b, blend : c)计算时,当我们使用R通道值作为第三个参数时,R通道灰度图中越亮的部分(值越接近1),在地形表现中越接近值b的结果。在上述代码中,使用R通道混合的是两个颜色值_ColorA和_ColorB。当我们取消其他的影响时,并设置两个颜色值左图所示时,我们可以预测R灰度图中越亮的部分对应到地形中则越接近白色(_ColorB),反之越暗的部分越接近红色(_ColorA),如右图所示:


以此原理,对应到四个通道,混合就会得到最终的效果。


最后,这篇教程里用到的textures是使用World Machine(有免费版,但是有限制)这个软件创建的。

【Unity Shaders】Using Textures for Effects——打包和混合textures的更多相关文章

  1. 【Unity Shaders】Using Textures for Effects介绍

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  2. 【Unity Shaders】Using Textures for Effects —— 实现Photoshop的色阶效果

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  3. 【Unity Shaders】Using Textures for Effects——让sprite sheets动起来

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  4. 【Unity Shaders】Using Textures for Effects——通过修改UV坐标来滚动textures

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  5. 【Unity Shaders】使用Unity Render Textures实现画面特效——画面特效中的亮度、饱和度和对照度

    本系列主要參考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同一时候会加上一点个人理解或拓展. 这里是本书全部的插图. 这里是本书所需的代码 ...

  6. 【Unity Shaders】《Unity Shaders and Effects Cookbook》总结篇

    我的唠叨 不知不觉,从发表第一篇关于<Unity Shaders and Effects Cookbook>已经快十个月了.一开始的初衷就是学习笔记,毕竟将来回过头去看的时候,再看英文难免 ...

  7. 【Unity Shaders】使用Unity Render Textures实现画面特效——建立画面特效脚本系统

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  8. 【Unity Shaders】Mobile Shader Adjustment—— 什么是高效的Shader

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  9. 【Unity Shaders】Lighting Models —— 衣服着色器

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

随机推荐

  1. 【DotNet加密方式解析】-- 好文收藏

    By -- 彭泽 一. DotNet加密方式解析--散列加密 笔记: 散列加密种类: 1.MD5  128位 2.SHA-1  160位 3.SHA-256  256位 4.SHA-384  384位 ...

  2. ACM Let the Balloon Rise

    Contest time again! How excited it is to see balloons floating around. But to tell you a secret, the ...

  3. MongoDB 固定集合

    MongoDB 固定集合(Capped Collections)是性能出色且有着固定大小的集合,对于大小固定,我们可以想象其就像一个环形队列,当集合空间用完后,再插入的元素就会覆盖最初始的头部的元素! ...

  4. Docker常见仓库Nginx

    Nginx 基本信息 Nginx 是开源的高效的 Web 服务器实现,支持 HTTP.HTTPS.SMTP.POP3.IMAP 等协议. 该仓库提供了 Nginx 1.0 ~ 1.7 各个版本的镜像. ...

  5. 虚拟机克隆,并设置新的ip

    6.1克隆新的虚拟机 选中某个虚拟机-à右键à管理à克隆 选择下一步 选择下一步 点击完成 6.2修改主机名 [root@hadoop3 ~]# vim/etc/sysconfig/network 将 ...

  6. Netty 4源码解析:服务端启动

    Netty 4源码解析:服务端启动 1.基础知识 1.1 Netty 4示例 因为Netty 5还处于测试版,所以选择了目前比较稳定的Netty 4作为学习对象.而且5.0的变化也不像4.0这么大,好 ...

  7. linux:CPU私有变量(per-CPU变量)

    一.简介2.6内核上一个新的特性就是per-CPU变量.顾名思义,就是每个处理器上有此变量的一个副本.per-CPU的最大优点就是,对它的访问几乎不需要锁,因为每个CPU都在自己的副本上工作.task ...

  8. Android简易实战教程--第四十五话《几种对话框》

    Android中提供了各种原生的对话框,在使用简单的功能的时候,还不比考虑自定义,使用原生的也能完成功能.本篇简单小案例就介绍三种对话框. 还是直接上代码吧: 布局中三个点击事件的按钮: <Li ...

  9. 【SSH系列】深入浅出SpringMvc+入门Demo

    Spring MVC框架是有一个MVC框架,通过实现Model-View-Controller模式来很好地将数据.业务与展现进行分离.从这样一个角度来说,Spring MVC和Struts.Strut ...

  10. Android布局概述

    布局 布局定义用户界面的视觉结构,如Activity或应用小部件的 UI.您可以通过两种方式声明布局: 在 XML 中声明 UI 元素.Android 提供了对应于 View 类及其子类的简明 XML ...