Unity3技术之表面着色

谁都知道在Unity中可以编写shader,可是他们很难写,尤其当你需要每个像素的灯光和阴影产生交互.在Unity3中,除了要包含旧的支持,你的shader也必须支持新的递延照明渲染(Deferred Lighting).所以我们决定要让shader更容易写!

一年多以前,我曾认为”着色器马死的快(呵呵,着色器必死,相关的帖子http://aras-p.info/blog/2009/05/05/shaders-must-die/,http://aras-p.info/blog/2009/05/07/shaders-must-die-part-2/,http://aras-p.info/blog/2009/05/10/shaders-must-die-part-3/)”.而现在我们正在开发Unity3,我们称之为表面着色(Surface Shaders),因为我觉得”着色器马死的快”做个这个新物种的名称不会得到老板认同.

主题思想就是在90%的情况下我之需要声明表面的特性就可以了.以后就想说:

嘿,反照率来自这个纹理和那个纹理的混合以及法线贴图.使用Blinn-Phong照明模式,别再来烦我了嗷!

像上面说的,我没有理会是否要提前或者延迟渲染,或者如何使用各种灯的类型去处理,或者要设置有多少效果将用灯光来呈现,或者用将有多少会产生实时阴影的部分,等等都不是我感兴趣的!这些肮脏的渲染工作怎么能让程序去做?!得让程序员解脱!

当然这不是一个新概念.大部分图形渲染编辑器都没用像素色(pixel color)去作为最后输出的节点,而是用了他们的一些可以描述他们的表面参数(弥漫,镜面,法线)作为节点,所有的灯光代码也不是针对作色器本身图形.OpenShadingLanguage(http://code.google.com/p/openshadinglanguage/)是和我们想的类似的一个想法,不过因为它是针对电影的脱机渲染,所有它更丰富更复杂.

下面是一个简单的例子,但它很完整,演示了Unity3的漫反射贴图和法线贴图的Shader.

Shader "Example/Diffuse Bump" {
    Properties {
      _MainTex ("Texture", 2D) = "white" {}
      _BumpMap ("Bumpmap", 2D) = "bump" {}
    }
    SubShader {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      #pragma surface surf Lambert
      struct Input {
        float2 uv_MainTex;
        float2 uv_BumpMap;
      };
      sampler2D _MainTex;
      sampler2D _BumpMap;
      void surf (Input IN, inout SurfaceOutput o) {
        o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
        o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
      }
      ENDCG
    }
    Fallback "Diffuse"
  }

将它赋予给漂亮的模型和贴图,它可以产生漂亮的图像!怎能不酷?!

这个是不是真的很有趣,将着色器的复杂语法简单化,后面再为一些老古董机器做一些后备声明.剩下的只有CG语言/HLSL的代码,然后通过自动生成最终代码提高图像效果.

我们来解析一下这段着色器代码:

  • #pragma surface surf Lambert:这是一个表面着色(surf)用的主要函数,和用Lambert照明模式.
  • struct Input:表面作色器输入的数据.这可以有多种定义,用来描述每个顶点和像素的对应关系.上面这段代码里它有两个纹理坐标.
  • surf函数,表面的实际着色代码.需要由SurfaceOutput(预定义结构)写入.它可以写入自定义结构,实际的代码只是写反照率和法线的输出.

什么是生成?

Unity的”表面着色器代码生成器”将利用这个方式产生实际的顶点和像素着色器,并将它编译好给各种目标平台.在Unity3中将默认支持这种着色器:

  • 提前渲染和递延渲染
  • 预计算对象物体有无光照.
  • 平行光,点光源,射灯的cookie阴影有无,实时光影的有无.
  • 对于提前渲染,它会在编译时计算每个顶点的灯的效果反映.
  • 延迟照明将会产生法线贴图,镜面贴图最后通过灯光的反射率和任何其它光照光影的反映.
  • 它可以选择性的生成一个阴影,比如一些复杂的通道贴图产生的阴影.

例如下面的代码通过提供的一个方向灯,4个顶点光源,三个阴影光被编译为一个提前渲染的可选光影的shader.我建议你滚动页面往下看看就好了:

#pragma vertex vert_surf
#pragma fragment frag_surf
#pragma fragmentoption ARB_fog_exp2
#pragma fragmentoption ARB_precision_hint_fastest
#pragma multi_compile_fwdbase
#include "HLSLSupport.cginc"
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
struct Input {
    float2 uv_MainTex : TEXCOORD0;
};
sampler2D _MainTex;
sampler2D _BumpMap;
void surf (Input IN, inout SurfaceOutput o)
{
    o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
    o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_MainTex));
}
struct v2f_surf {
  V2F_POS_FOG;
  float2 hip_pack0 : TEXCOORD0;
  #ifndef LIGHTMAP_OFF
  float2 hip_lmap : TEXCOORD1;
  #else
  float3 lightDir : TEXCOORD1;
  float3 vlight : TEXCOORD2;
  #endif
  LIGHTING_COORDS(3,4)
};
#ifndef LIGHTMAP_OFF
float4 unity_LightmapST;
#endif
float4 _MainTex_ST;
v2f_surf vert_surf (appdata_full v) {
  v2f_surf o;
  PositionFog( v.vertex, o.pos, o.fog );
  o.hip_pack0.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
  #ifndef LIGHTMAP_OFF
  o.hip_lmap.xy = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
  #endif
  float3 worldN = mul((float3x3)_Object2World, SCALED_NORMAL);
  TANGENT_SPACE_ROTATION;
  #ifdef LIGHTMAP_OFF
  o.lightDir = mul (rotation, ObjSpaceLightDir(v.vertex));
  #endif
  #ifdef LIGHTMAP_OFF
  float3 shlight = ShadeSH9 (float4(worldN,1.0));
  o.vlight = shlight;
  #ifdef VERTEXLIGHT_ON
  float3 worldPos = mul(_Object2World, v.vertex).xyz;
  o.vlight += Shade4PointLights (
    unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
    unity_LightColor0, unity_LightColor1, unity_LightColor2, unity_LightColor3,
    unity_4LightAtten0, worldPos, worldN );
  #endif // VERTEXLIGHT_ON
  #endif // LIGHTMAP_OFF
  TRANSFER_VERTEX_TO_FRAGMENT(o);
  return o;
}
#ifndef LIGHTMAP_OFF
sampler2D unity_Lightmap;
#endif
half4 frag_surf (v2f_surf IN) : COLOR {
  Input surfIN;
  surfIN.uv_MainTex = IN.hip_pack0.xy;
  SurfaceOutput o;
  o.Albedo = 0.0;
  o.Emission = 0.0;
  o.Specular = 0.0;
  o.Alpha = 0.0;
  o.Gloss = 0.0;
  surf (surfIN, o);
  half atten = LIGHT_ATTENUATION(IN);
  half4 c;
  #ifdef LIGHTMAP_OFF
  c = LightingLambert (o, IN.lightDir, atten);
  c.rgb += o.Albedo * IN.vlight;
  #else // LIGHTMAP_OFF
  half3 lmFull = DecodeLightmap (tex2D(unity_Lightmap, IN.hip_lmap.xy));
  #ifdef SHADOWS_SCREEN
  c.rgb = o.Albedo * min(lmFull, atten*2);
  #else
  c.rgb = o.Albedo * lmFull;
  #endif
  c.a = o.Alpha;
  #endif // LIGHTMAP_OFF
  return c;
}

在90行的代码中,10行是原始的着色器代码,其余的代码在Unity2的时候几乎都要手写的.别急,这只是写了一点提前渲染的部分!还需要写递延渲染,可选阴影等等灯.

因此,现在应该会比较容易的写着色器(对于我来说至少).Unity的用户也愿意少写代码,少些至少三倍的代码何乐不为?它还会有一些改变以适应Unity下一步新的照明系统.

预定义的输入值

输入结构可以包含纹理坐标和一些预定义的值,例如查看的方向,世界空间的位置,世界空间的反射矢量灯.例如,如果你利用世界空间反射矢量(如自发光),在模型表面做一些CubeMap反射效果(递延渲染的反射将不会计算,因为它不会产生效果,所以也不需要考虑反射向量),

用一个小例子说明做简单的灯光渲染:

#pragma surface surf Lambert
  struct Input {
      float2 uv_MainTex;
      float2 uv_BumpMap;
      float3 viewDir;
  };
  sampler2D _MainTex;
  sampler2D _BumpMap;
  float4 _RimColor;
  float _RimPower;
  void surf (Input IN, inout SurfaceOutput o) {
      o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
      o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
      half rim =
          1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));
      o.Emission = _RimColor.rgb * pow (rim, _RimPower);
  }

顶点修改器

它可以自定义顶点修饰功能,用来修改或者产生每个顶点的数据.像风吹动树的动画,草的摇摆等等.

我最喜欢沿着法线顶点的方向去移动顶点.像打肿了的效果…

自定义光照模型

有一个内置的简单的光照模型,你也可以指定你自己的.A照明模式只不过将常用的光的参数,方向衰减,而没包含一些提前渲染和延迟渲染以及一些不长用到的方法.因此,对于任何奇特的效果你都要通过提供自定义光照模型来完成.

例如wrapped-Lambert光照模式:
  #pragma surface surf WrapLambert
  half4 LightingWrapLambert (SurfaceOutput s, half3 dir, half atten) {
      dir = normalize(dir);
      half NdotL = dot (s.Normal, dir);
      half diff = NdotL * 0.5 + 0.5;
      half4 c;
      c.rgb = s.Albedo * _LightColor0.rgb * (diff * atten * 2);
      c.a = s.Alpha;
      return c;
  }
  struct Input {
      float2 uv_MainTex;
  };
  sampler2D _MainTex;
  void surf (Input IN, inout SurfaceOutput o) {
      o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
  }

幕后

我们使用Ryan Gordon的mojoshder(http://hg.icculus.org/icculus/mojoshader/)中的HLSL解析器来分析原始的表面着色器代码.找出有哪些成员结构,有哪些函数原型等等.在这个阶段做的一些错误检查是为了告诉用户它的面函数原型是错误或者他的结构缺少必要的成员.

为了弄清楚着色器实际的照明关系,我们做了像素着色器的小工具来编译CG语言和使用CG语言的API查询使用了哪些输入输出.这样我们可以算出例如一个法线贴图实际不用递延照明并也节省了一些顶点着色器指令和一个纹理坐标值.

生成的代码最终根据目标平台被编译.Cg为Windows/Mac,XDK HLSL为Xbox 360,PS3 Cg为PS3,和我们自己创造的HLSL2GLSL为iPhone, Android和即将退出的Unity NativeClient(http://blogs.unity3d.com/2010/05/19/google-android-and-the-future-of-games-on-the-web/).

就这样,我们会继续做下去,等到Unity3发布的时候,我希望更多的人会尝试写着色器!

转载请注明来自1Vr.Cn,原文地址:http://blogs.unity3d.com/2010/07/17/unity-3-technology-surface-shaders/

Unity3 beta3试用笔记

昨天Unity发布了Unity3的第三版测试.赶紧更新一用.安装后被首次出现在Unity的黑色界面吓了一跳.玩酷嘛,哈哈.不过虽然是很酷,与新版Maya,Max的黑色界面相似.可是我感觉累眼睛.可能字体太小的缘故.

脚本上上了升级,也进行了一些修改.比如原来的iPhoneTouch命令,现在去掉了iPhone,只剩下Touch,我觉得是因为为了更好的支持Android而进行的如此改进,总不能在Android上也试用iPhoneTouch去点击屏幕吧.嘿嘿.

beta3中造树系统修正了bug,更加易用.可以方便的建立各种造型的树木,且可以像普通游戏物体一样使用,不限于只能用在地形上.beta3为了让树木更加的真实,增加了风系统,原来Unity中也有风力,但是是全局效果的,整个世界中只有一个方向的大风.而现在,你可以让你游戏世界中各种各样的风,甚至造沙尘暴.哈哈.开玩笑.

布料系统很简单,给你需要做成布料的物体添加这个组件既可.弄个拟真的窗帘.弄个桌布,不在话下.

重点来喽,beta3中带来了新版Webplayer插件.最显而易见的改变就是体积改变.不用我多说.看图就知道了.原来Win版插件3M之大,现在500多k的大小,足以在客户面前骄傲的说Unity的插件比Flash小多了!!而且还是3D的播放器啊!

在Unity增加的重量级功能就是Lightmapper,也是很方便的使用.省略了以前在Max中各种复杂的烘培步骤.不过将它用在实际工作中,在Unity中打灯渲染,我还是持观望态度.

Unity另一个我们认为巨大改进就是支持系统字体渲染和输入法的支持,开发网页游戏的中文聊天就不会那么头疼了.可以抛弃掉巨大的字库累赘.不过iPhone版目前还是不支持这个功能.

[/align]

这个材质球目前用起来很不习惯,而且检视面板中没有了贴图预览.估计beta3中不小心遗漏了.这个比较麻烦.

[/align]

今天就说这么多吧,最后附上Build窗口,看看强大的Unity3多平台支持!还有Wii,截图上没体现.

ps:北京下雨了,终于下雨了,闷热的天气终于凉爽了好多.毛毛雨中散步.很好.有个妞子陪伴更爽了.哈哈.

有关Unity和iOS4.0的新说明,以及Unity3支持mod格式

亲爱的Unity用户,我再次感谢你们的耐心.感谢你们的支持!

很多人都对开发移动设备兴趣很大,特别是使用Unity创造iPhone游戏而叫棒!,我们一直密切的关注这iOS 4.0的开发服务条款.虽然我们也相信Unity使用C#和JavaScript会没问题的.有一些没有被苹果公司证实的小道消息.不过,苹果公司今天仍审批Unity开发的游戏,也挑选了一些优秀的Unity游戏做推荐.一直以来我们也投入大的经历和资源在Unity iPhone的开发,其中包括即将到来的Unity3.0的许多新功能.但是由于新的开发服务条款的公布,我们也开始了一项应急方案的工作,以防万一.现在请允许解释一下这应急计划的具体内容,让每个人都知道”B计划”的模样.

你可能知道,Unity大多是通过C++优化和通过Objective-C封装的.游戏逻辑是由开发者用C#和JavaScript写的,这两者都运行在.NET中.这个方案的优点在于,我们能够不用复杂的语言实现我们想要的功能,.NET能快速的开发,且接近于即时的编译,又能在同一时间生成高度优化的代码.并且在iPhone上最终编译成符合旧的iOS服务条款的静态机器码.此外iPhone也很容易利用Objective-C代码去访问API,诸如游戏中心等等,这真的是一个两全其美的事情.

由于Unity支持.NET可能与服务的新条款冲突,我们正在研究一种可以在整个游戏中不创建任何使用.NET代码的解决方案.这种情况下所有的脚本API使用c++来操纵.这当然不理想,像有代码量会加长很多.相比C#和JavaScript,C++无疑复杂的多.

但老实说,这并不太糟糕.Unity仍旧有资源管理,着色语言等一些列工具,当然还有引擎的优化.我们也同样保持了Unity的JavaScript和C#的特点:享受似的开发工作.变量仍旧可以在检视面板中去定义.从本质上讲我们正在创建一个基于C++的.NET编译器,使得我们能纯粹的托管C++在Webplayer和其它平台上.在iOS中C++代码将被苹果的Xcode工具所编译.这确实是非常强大的组合.在Unity的编辑器,你可以快速的高效率的开发,在设备上你又可以拥有纯粹的native C.

但是,事实上,当你做一个简单的游戏逻辑时,C++并不是十分复杂,让我们看几个不同的例子.

这是一个非常简单的JavaScript脚本,用来旋转游戏中的物体.

这是用C++编写相同功能的代码.

正如你可以它们基本相同,并不是差别很大.再举个复杂点的例子
这是在iOS设备上利用重力感应和触摸来控制飞行器飞行和发射导弹的游戏:

下面的同样是由C++来实现相同的功能:

同样,与JavaScript对比,我们没有在C++中写太多的代码,甚至与C#相比,它们的差异更小.

我们将继续关注以Unity开发iPhone,iPod touch和iPad.虽然我们不认为C++是编写游戏代码的最好语言,但是C++在内存占用和设备性能上的巨大优势,这是一个伟大的特点,作为开发者,谁不想更多的减少内存占用和提高游戏的性能能呢.

我们仍然不能相信苹果将迫使选择一个具体的语言去开发.正如我先前所说.苹果公司仍然能通过审批Unity开发的游戏,可你们也应该放心,如果形势有变化,我们也不用太担心,因为我们已经准备了B计划.

原文:http://blogs.unity3d.com/2010/07/02/unity-and-ios-4-0-update-iii/

—————————————————–
在Unity3.0中,我们添加了一个令人振奋的新消息,就是用户能在Unity中能像使用其它资源一样使用*.mod文件中的音乐和声音了.先看看Unity3的视音频功能预览视频吧.http://blogs.unity3d.com/2010/06/25/unity-3-feature-preview-audio/

当我开始测试Unity新的音频功能的时候,对*.mod文件我只有基本没什么想法.也不知道能做些什么.我就问我们的音频程序员Søren,他说这文件格式体积小,音质还好.所以我肯定和他说让他试试.当比较了文件大小和音频质量后,我很惊讶,我认为这更适合在网页游戏中传输使用.

这条博文翻译不下去了,太多专业短语不会翻译.放弃了..反正就是说Unity3中支持*.mod文件了.

ps:翻译注明来处1vr.cn,否则MJJ!!

Unity开发Pc单机游戏硬件码注册实现分享

前阵子写的一个单机注册插件,看Unity群里有朋友要求共享,就整理了一下,发到这里共享给大家.本程序相关代码用易语言黑月编写.我觉得什么工具不重要,重要的是能得到结果.是吧.

可以实现的效果如上图,功能包含单机硬件码注册,试用时限限制.取的是客户机的CPU码作为硬件码.经过打乱后显示给客户端的RegBox.exe,客户将硬件码提供给作者,作者利用算号器Keygen.exe算出符合该硬件码的序列号再发给客户,客户打开客户端注册程序RegBox.exe,输入序列号,将信息存入客户机(含有软件到期时间),此时不做验证.当运行Unity游戏时,验证序列号是否正确且验证是否超出试用时限.不同状态有不同返回值.方便作者在Unity中做响应.

在压缩包的BuildExe目录是已经编译好的文件.默认情况下在Unity中直接调用CheckAppSn.dll处理判断,分发时附上RegBox.exe既可.但如果想更改序列号计算方式以免与其它同样使用这个插件的作者重复的话.你可以自行修改计算方式.这个也非常简单.

本插件dll及exe源码需要易语言+黑月打开.黑月工具下载地址(http://www.basic8.com.cn/post/1.html),修打开RegDll.e及Keygen.e代码,修改里面入下图部分既可.其中类似1234567这种数字都可以随意修改,相当于加密密钥.保证Regdll与Regbox中一致就行了.

本插件也包含了注册时间限制,默认是限制软件只有三个月试用时间.如修改时间长度需要打开RegBox.e代码后修改如下部分.

如果不需要时间限制这个功能,可以打开Regdll.e源码,注释掉如下判断段既可.

附件的Project目录中是Unity调用插件的项目文件.E_Code目录是插件的易语言源文件.

ps:如提示有病毒,基本可以忽略,易语言被误杀是历史遗留问题.不是我能解决的.呵呵.

附件:
本地下载:点击下载此文件
镜像下载:http://u.115.com/file/f03b46cacd