本系列主要参考《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,和一个用于调整整体地形颜色的值。

    	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) = ""{}
    }
  2. 在SubShader中创建8个变量,分别对应Properties中的8个properties,以建立和它们之间的链接。
    		CGPROGRAM
    #pragma surface surf Lambert float4 _MainTint;
    float4 _ColorA;
    float4 _ColorB;
    sampler2D _RTexture;
    sampler2D _GTexture;
    sampler2D _BTexture;
    sampler2D _BlendTex;
    sampler2D _ATexture;
  3. 为了根据每一张不同的texture来改变其在地形上的tiling rates(平铺率,可以理解为地上某些区域草比较密集,某些地区石头比较多等),我们需要修改结构体。
    		struct Input {
    float2 uv_RTexture;
    float2 uv_GTexture;
    float2 uv_BTexture;
    float2 uv_ATexture;
    float2 uv_BlendTex;
    };
  4. 在surf函数中,得到每张texture的信息,并分别存储在它们对应的变量中。
    			//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);
  5. 使用lerp函数将四张texture混合。lerp函数有三个参数,lerp(value
    : a, value : b, blend : c)。它从前两个参数中得到数据,并使用最后一个参数混合前两个值。

    			//No 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;
  6. 最后,我们使用blending texture的R通道值混合两个颜色色调值,并将结果与之前的混合值相乘。
    			//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;

整体代码如下:
Shader "Custom/TextureBlending" {
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" }
LOD 200 CGPROGRAM
#pragma surface surf Lambert float4 _MainTint;
float4 _ColorA;
float4 _ColorB;
sampler2D _RTexture;
sampler2D _GTexture;
sampler2D _BTexture;
sampler2D _BlendTex;
sampler2D _ATexture; struct Input {
float2 uv_RTexture;
float2 uv_GTexture;
float2 uv_BTexture;
float2 uv_ATexture;
float2 uv_BlendTex;
}; void surf (Input IN, inout SurfaceOutput o) {
//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); //No 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; //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"
}

我们新建一个地形,并把创建的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. 网站用户身份识别俩大招之cookie

    导航: 原理介绍 代码实现 过程分析 追踪Cookie 原理介绍 众所周知,http协议是无状态的协议,简单理解是用户的前一步操作和后一步操作之间没有关系,互相不知道,不干扰.而在很多场景下,浏览网页 ...

  2. ubuntu下安装 python 常用软件

    1.用于科学计算的常用包: sudo apt-get install python-numpy python-scipy python-matplotlib ipython ipython-noteb ...

  3. 0428css样式

    CSS样式表|-引入的三种方式|--内联样式|----标签内部(空格style)|--内嵌样式|----<head></head>标签内部(<style></ ...

  4. Docker 控制组

    控制组(cgroups)是 Linux 内核的一个特性,主要用来对共享资源进行隔离.限制.审计等.只有能控制分配到容器的资源,才能避免当多个容器同时运行时的对系统资源的竞争. 控制组技术最早是由 Go ...

  5. Python3 MySQL 数据库连接

    什么是 PyMySQL? PyMySQL 是在 Python3.x 版本中用于连接 MySQL 服务器的一个库,Python2中则使用mysqldb. PyMySQL 遵循 Python 数据库 AP ...

  6. 在Spring Boot中使用数据缓存

    春节就要到了,在回家之前要赶快把今年欠下的技术债还清.so,今天继续.Spring Boot前面已经预热了n篇博客了,今天我们来继续看如何在Spring Boot中解决数据缓存问题.本篇博客是以初识在 ...

  7. SQL实例整理

    本文适合将w3school的SQL教程(http://www.w3school.com.cn/sql/sql_create_table.asp)都基本看过一遍的猿友阅读. 说说博主的情况吧.毕业找工作 ...

  8. solr界面

    1.1 界面功能介绍 1.1.1 Analysis

  9. SQL Server 执行计划操作符详解(3)——计算标量(Compute Scalar)

    接上文:SQL Server 执行计划操作符详解(2)--串联(Concatenation ) 前言: 前面两篇文章介绍了关于串联(Concatenation)和断言(Assert)操作符,本文介绍第 ...

  10. memcached实战系列(三)memcached命令使用

    memcached命令的使用,在这里我们最好了解一下命令的含义,对命令有一个大致的了解,在了解的基础上进行使用.这里的命名是常用的crud命令的演示. 1.1.1. memcached命令的格式 标准 ...