unity shader matrix怎么得到对象的matrix

3113人阅读
Unity 3D(145)
//实现思路:鼠标点击,产生目标点,计算角色和目标点的夹角,旋转角色朝向目标点,然后移动角色。
void Update ()
if(Input.GetMouseButtonDown(0))
RayControl();
if(flagMove)
if(Vector3.Distance(transform.position,mousePos)&1)
transform.Translate(transform.worldToLocalMatrix* transform.forward * Time.deltaTime*5);
//transform.forward是世界坐标,通过transform.worldToLocalMatrix转换矩阵转到本地坐标 然后在本地坐标运动,没有必要必须在本地坐标系运动 但是必须注意要统一起来。
void RayControl()
Ray ray=Camera.main.ScreenPointToRay(Input.mousePosition);
//向屏幕发射一条射线
if(Physics.Raycast(ray,out hit,200))射线长度为200 和地面的碰撞盒做检测
GameObject targetPos=GameObject.CreatePrimitive(PrimitiveType.Sphere);//实例化一个Sphere
targetPos.transform.localScale=new Vector3(0.5f,0.5f,0.5f);
mousePos=hit.//获取碰撞点坐标
mousePos.y=transform.position.y;
targetPos.transform.position=mouseP//Sphere放到鼠标点击的地方
targetDir=mousePos-transform.//计算出朝向
Vector3 tempDir=Vector3.Cross(transform.forward,targetDir.normalized);//用叉乘判断两个向量 是否同方向
float dotValue=Vector3.Dot(transform.forward,targetDir.normalized);//点乘 计算两个向量的夹角,及角色和目标点的夹角
float angle=Mathf.Acos(dotValue)*Mathf.Rad2D
if(tempDir.y&0)//这块 说明两个向量方向相反,这个判断用来确定 假如两个之间夹角30度 到底是顺时 还是逆时针旋转。
angle=angle*(-1);
print(tempDir.y);
print(&2:&+angle);
transform.RotateAround(transform.position,Vector3.up,angle);
//注意事项:写完了才发现 原来没必要这么麻烦,这样transform.forward=(mousePos-transform.position).normalized 就可以直接让角色朝向目标点 RayControl函数 好多都可以省了
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:707458次
积分:9209
积分:9209
排名:第1409名
原创:179篇
转载:291篇
评论:69条
难度:中级
类型:实战教学
难度:中级
类型:技术教程
难度:中级
类型:技术教程
(2)(2)(1)(1)(1)(1)(98)(1)(1)(1)(1)(1)(1)(8)(7)(67)(82)(11)(1)(15)(3)(2)(8)(5)(1)(5)(10)(37)(34)(22)(10)(6)(3)(2)(8)(1)(3)(7)23631人阅读
Unity3D(19)
转发,请保持地址:
相关文章:
这篇文章将收集unity的相关技巧,会不断地更新内容。
1. 保存运行中的状态
unity在运行状态时是不能够保存的。但在运行时编辑的时候,有时会发现比较好的效果想保存。这时可以在 “Hierarchy”中复制相关对象树,暂停游戏后替换原来的,就可以了。(其实这个拷贝过程是序列化过程,这种方法是序列化到内存中;另外一种方法就是序列化到磁盘上,即把内容拖动到文件夹中变成prefab,效果也是一样的)
2. Layer的用法
LayerMask.NameToLayer(&Ground&);
&// 通过名字获取layer
<span style="color:#D Raycast
if(Physics.Raycast(cam3d.ScreenPointToRay(Input.mousePosition), out hit, Mathf.Infinity, (1&&LayerMask.NameToLayer(&Ground&)))) {
2D Raycast
Collider2D h = Physics2D.OverlapPoint(Input.mousePosition, (1&&LayerMask.NameToLayer(&xxx&)));
uGUI中的做法:
void Update () {
if (Input.GetMouseButtonDown (0)) {
if (EventSystem.current.IsPointerOverGameObject ()) {
PointerEventData pe = new PointerEventData(EventSystem.current);
pe.position =
Input.mouseP
List&RaycastResult& hits = new List&RaycastResult&();
EventSystem.current.RaycastAll( pe, hits );
foreach(RaycastResult h in hits) {
if(h.gameObject.layer == tangramLayer) {
Debug.Log(&======& + Time.time + &, obj: & + h.gameObject.name);
..................
3. 物理摄像头取色(WebCamTexture)
Texture2D exactCamData() {
// get the sample pixels
Texture2D snap = new Texture2D((int)detectSize.x, (int)detectSize.y);
snap.SetPixels(webcamTexture.GetPixels((int)detectStart.x, (int)detectStart.y, (int)detectSize.x, (int)detectSize.y));
snap.Apply();
保存截图:
System.IO.File.WriteAllBytes(Application.dataPath + &/test.png&, exactCamData().EncodeToPNG());
4. 操作componenent
CircleCollider2D cld = (CircleCollider2D)colorYuan[i].AddComponent(typeof(CircleCollider2D));
cld.radius = 1;
Destroy(transform.gameObject.GetComponent&SpriteRenderer&());
5. 动画相关
状态Init到状态fsShake的的条件为:参数shake==true;代码中的写法:
触发fsShake:
void Awake() {
anims = new Animator[(int)FColorType.ColorNum];
if(needShake) {
curAnim.SetTrigger(&shake&);
关闭fsShake
void Update() {
if(curAnim) {
AnimatorStateInfo stateInfo = curAnim.GetCurrentAnimatorStateInfo(0);
if(stateInfo.nameHash == Animator.StringToHash(&Base Layer.fsShake&)) {
curAnim.SetBool(&shake&, false);
print (&======&&&&& stop shake!!!!&);
关于Animator.StringToHash函数的说明:
animator的setBool,setTrigger等函数都有两种参数形式,一个接收string,一个接收hash;其实Animator内部是使用hash来记录相应信息的,所以接收string类型的函数内部会帮你调用StringToHash这个函数;所以如果要经常调用setTrigger等,请把参数名称hash化保持以便反复使用,从而提高性能。
在状态机animator中获取animation state 和animation clip的方法(参考):
public static AnimationClip getAnimationClip(Animator anim, string clipName) {
UnityEditorInternal.State state = getAnimationState(anim, clipName);
return state!=null ? state.GetMotion() as AnimationClip :
public static UnityEditorInternal.State getAnimationState(Animator anim, string clipName) {
UnityEditorInternal.State state =
if(anim != null) {
UnityEditorInternal.AnimatorController ac = anim.runtimeAnimatorController as UnityEditorInternal.AnimatorC
UnityEditorInternal.StateMachine sm = ac.GetLayer(0).stateM
for(int i = 0; i & sm.stateC i++) {
UnityEditorInternal.State _state = sm.GetState(i);
if(state.uniqueName.EndsWith(&.& + clipName)) {
6. scene的切换
同步方式:
Application.LoadLevel(currentName);
异步方式:
Application.LoadLevelAsync(&ARScene&);
7. 加载资源
Resources.Load&Texture&(string.Format(&{0}{1:D2}&, mPrefix, 5));
8. Tag VS. Layer
& & &-& Tag用来查询对象
& & &-& Layer用来确定哪些物体可以被raycast,还有用在camera render中
transform.eulerAngles 可以访问 rotate的 xyz
transform.RotateAround(pivotTransVector, Vector3.up, -0.5f * (tmp-preX) * speed);
10. 保存数据
PlayerPrefs.SetInt(&isInit_& + Application.loadedLevelName, 1);
11. 动画编码
/lopezycj/archive//Unity3d_AnimationEvent.html
/Components/animeditor-AnimationEvents.html
/questions/8172/how-to-add-new-curves-or-animation-events-to-an-im.html
12. 遍历子对象
Transform[] transforms = target.GetComponentsInChildren&Transform&();
for (int i = 0, imax = transforms.L i & ++i) {
Transform t = transforms[i];
t.gameObject.SendMessage(functionName, gameObject, SendMessageOptions.DontRequireReceiver);
13. 音效的播放
&先添加Auido Source, 设置Audio Clip, 也可以在代码中加入。然后在代码中调用audio.Play(), 参考如下代码:
public AudioClip aC
void Start () {
audio.clip = aC
audio.Play();
另外,如果是3d音效的话,需要调整audio Souce中的panLevel才能听到声音,不清楚原因。
14. 调试技巧(Debug)
可以在OnDrawGizmos函数来进行矩形区域等,达到调试的目的,请参考NGUI中的UIDraggablePanel.cs文件中的那个函数实现。
#if UNITY_EDITOR
/// &summary&
/// Draw a visible orange outline of the bounds.
/// &/summary&
void OnDrawGizmos ()
if (mPanel != null)
Bounds b =
Gizmos.matrix = transform.localToWorldM
Gizmos.color = new Color(1f, 0.4f, 0f);
Gizmos.DrawWireCube(new Vector3(b.center.x, b.center.y, b.min.z), new Vector3(b.size.x, b.size.y, 0f));
15. 延时相关( StartCoroutine)
StartCoroutine(DestoryPlayer());
IEnumerator DestoryPlayer() {
Instantiate(explosionPrefab, transform.position, transform.rotation);
gameObject.renderer.enabled =
yield return new WaitForSeconds(1.5f);
gameObject.renderer.enabled =
16. Random做种子
UnityEngine.Random.seed = (int)System.DateTime.Now.ToUniversalTime().ToBinary();
17. &调试技巧(debug),可以把&#20540;方便地在界面上打印出来
void OnGUI() {
GUI.contentColor = Color.
GUILayout.Label(&deltaTime is: & + Time.deltaTime);
18. 分发消息
sendMessage,&BroadcastMessage等
19. 游戏暂停(对timeScale进行设置)
Time.timeScale = 0;
20. 实例化一个prefab
Rigidbody2D propInstance = Instantiate(backgroundProp, spawnPos, Quaternion.identity) as Rigidbody2D;
21. Lerp函数的使用场景
// Set the health bar&#39;s colour to proportion of the way between green and red based on the player&#39;s health.
healthBar.material.color = Color.Lerp(Color.green, Color.red, 1 - health * 0.01f);
22. 在特定位置播放声音
// Play the bomb laying sound.
AudioSource.PlayClipAtPoint(bombsAway,transform.position);
23. 浮点数相等的判断
(由于浮点数有误差, 所以判断的时候最好不要用等号,尤其是计算出来的结果)
if (Mathf.Approximately(1.0, 10.0/10.0))
print (&same&);
24. 通过脚本修改shader中uniform的&#20540;
//shader的写法
Properties {
disHeight (&threshold distance&, Float) = 3
SubShader {
#pragma vertex vert
#pragma fragment frag
uniform float disH
// ===================================
// 修改shader中的disHeight的值
gameObject.renderer.sharedMaterial.SetFloat(&disHeight&, height);
25. 获取当前level的名称
Application.loadedLevelName
26. 双击事件
void OnGUI() {
Event Mouse = Event.
if ( Mouse.isMouse && Mouse.type == EventType.MouseDown && Mouse.clickCount == 2) {
print(&Double Click&);
27. 日期:
System.DateTime dd = System.DateTime.N
GUILayout.Label(dd.ToString(&M/d/yyyy&));
28. RootAnimation中移动的脚本处理
class RootControl : MonoBehaviour {
void OnAnimatorMove() {
Animator anim = GetComponent&Animator&();
if(anim) {
Vector3 newPos = transform.
newPos.z += anim.GetFloat(&Runspeed&) * Time.deltaT
transform.position = newP
29. BillBoard效果
(广告牌效果,或者向日葵效果,使得对象重视面向摄像机)
public class BillBoard : MonoBehaviour {
// Update is called once per frame
void Update () {
transform.LookAt(Camera.main.transform.position, -Vector3.up);
另外,Shader的实现方案请参考:
30. script中的属性编辑器
(Property Drawers),还可以自定义属性编辑器
其中Popup好像无效,但是enum类型的变量,能够达到Popup的效果
public class Example : MonoBehaviour {
public string playerName = &Unnamed&;
[Multiline]
public string playerBiography = &Please enter your biography&;
[Popup (&Warrior&, &Mage&, &Archer&, &Ninja&)]
public string @class = &Warrior&;
[Popup (&Human/Local&, &Human/Network&, &AI/Easy&, &AI/Normal&, &AI/Hard&)]
[Range (0, 100)]
public float health = 100;
[Regex (@&^(?:\d{1,3}\.){3}\d{1,3}$&, &Invalid IP address!\nExample: &#39;127.0.0.1&#39;&)]
public string serverAddress = &192.168.0.1&;
public Vector3 forward = Vector3.
public Vector3 target = new Vector3 (100, 200, 300);
public ScaledC
public ScaledC
public float turnRate = (Mathf.PI / 3) * 2;
31. Mobile下面使用lightmapping问题的解决方案
在mobile模式下,lightmapping可能没有反应,可以尝试使用mobile下的shader,可以解决问题。更多请参考:
32. &Unity下画线的功能(用于debug)
Debug.DrawLine (Vector3.zero, new Vector3 (10, 0, 0), Color.red);
33. Shader中代码的复用(CGINCLUDE的使用)
Shader &Self-Illumin/AngryBots/InterlacePatternAdditive& {
Properties {
_MainTex (&Base&, 2D) = &white& {}
#include &UnityCG.cginc&
sampler2D _MainT
struct v2f {
half4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
half2 uv2 : TEXCOORD1;
v2f vert(appdata_full v) {
fixed4 frag( v2f i ) : COLOR {
return colorT
SubShader {
Tags {&RenderType& = &Transparent& &Queue& = &Transparent& &Reflection& = &RenderReflectionTransparentAdd& }
ZWrite Off
Blend One One
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
FallBack Off
34. 获取AnimationCurve的时长
_curve.keys[_curve.length-1].
35. C#中string转变成byte[]:
byte[] b1 = System.Text.Encoding.UTF8.GetBytes (myString);
byte[] b2 = System.Text.Encoding.ASCII.GetBytes (myString);
System.Text.Encoding.Default.GetBytes(sPara)
new ASCIIEncoding().GetBytes(cpara);
char[] cpara=new char[bpara.length];
for(int i=0;i &bpara.i ++){char[i]=system.convert.tochar(bpara[i]);}
list.Sort(delegate(Object a, Object b) { return pareTo(b.name); });
37. NGUI的相关类关系:
38. 使得脚本能够在editor中实时反映:
在脚本前加上:[ExecuteInEditMode], 参考UISprite。
39. 隐藏相关属性
属性前加上 [HideInInspector],在shader中也适用;
public class ResourceLoad : MonoBehaviour {
[HideInInspector] public string ressName = &Sphere&;
public string baseUrl = &http://192.168.56.101/ResUpdate/{0}.assetbundle&;Shader &stalendp/imageShine& {
Properties {
[HideInInspector] _image (&image&, 2D) = &white& {}
_percent (&_percent&, Range(-5, 5)) = 1
_angle(&_angle&, Range(0, 1)) = 0
40. 属性的序列化
可被序列化的属性,可以显示在Inspector面板上(可以使用HideInInspector来隐藏);public属性默认是可被序列化的,private默认是不可序列化的。使得private属性可被序列化,可以使用[SerializeField]来修饰。如下:
[SerializeField] private string ressName = &Sphere&;
41. Shader编译的多样化()
shader通常如下的写法:
#pragma multi_compile FANCY_STUFF_OFF FANCY_STUFF_ON
这样可以把Shader编译成多个版本。使用的时候,局部设置Material.EnableKeyword, DisableKeyword;或则 全局设置Shader.EnableKeyword and DisableKeyword进行设置。
42. 多个scene共享内容
void Awake() {
DontDestroyOnLoad(transform.gameObject);
}43. 使用Animation播放unity制作的AnimationClip:
首先需要设置AnimationType的类型为1 (需要在animationClip的Debug模式的属性中进行设置,如下图。另外,如果需要在animator中播放动画,需要类型为2);
代码如下:
string clipName = &currentClip&;
AnimationClip clip = ...;
// 设置animationClip
if (anim == null) {
anim = gameObject.AddComponent&Animation&();
anim.AddClip(clip, clipName);
anim[clipName].speed = _speedS
// 计算时间
float duration = anim[clipName].
duration /= _speedS
// 播放动画
anim.Play(clipName);
yield return new WaitForSeconds(duration + 0.1f);
// 动画结束动作
anim.Stop();
anim.RemoveClip(clipName);
44. RenderTexture的全屏效果
MeshFilter mesh = quard.GetComponent&MeshFilter& ();
// 创建和摄像机相关的renderTexture
RenderTexture rTex = new RenderTexture ((int)cam.pixelWidth, (int)cam.pixelHeight, 16);
cam.targetTexture = rT
mesh.renderer.material.mainTexture = rTshader端:
#include &UnityCG.cginc&
sampler2D _MainT
sampler2D _NoiseT
struct v2f {
half4 pos:SV_POSITION;
half2 uv : TEXCOORD0;
float4 srcPos: TEXCOORD1;
v2f vert(appdata_full v) {
o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
o.uv = v.texcoord.
o.srcPos = ComputeScreenPos(o.pos);
fixed4 frag(v2f i) : COLOR0 {
float tmp = saturate(1-(length(i.uv-float2(0.5,0.5)))*2)*0.04;
float2 wcoord = (i.srcPos.xy/i.srcPos.w) + tex2D(_NoiseTex, i.uv) * tmp - tmp/2;
return tex2D(_MainTex, wcoord);
45)使用RenderTexture制作屏幕特效:
[ExecuteInEditMode]
public class MyScreenEffect : MonoBehaviour {
void OnRenderImage(RenderTexture source, RenderTexture dest) {
Graphics.Blit(source, dest, material);
}如果需要多次渲染,需要使用RenderTexture.GetTemporary生成临时的RenderTexture,这个方法会从unity维护的池中获取,所以在使用完RenderTexture后,尽快调用RenderTexture.ReleaseTemporary使之返回池中。 RenderTexture存在于GPU的DRAM中,具体实现可以参考Cocos2dx中的写法来理解(CCRenderTexture.cpp中有具体实现)。GPU的架构请参考:
46. 特定条件下暂停unity
在调试战斗的时候,需要在特定条件下暂停unity,以便查看一些对象的设置。可以使用Debug.Break();来达到目的。这是需要处于debug模式,才能够达到效果。
47. 在特定exception发生时暂停执行;
默认情况下,unity在遇到异常时,是不会停止执行的。但是如果要在nullReferenceException等exception发生时,进行调试。可以在MonoDevelop的菜单中选择Run/Exceptions..., 做相关设置:
48. NGUI性能建议
相关知识:1)在NGUI中,一个Panel对应一个DrawCall(当然一个panel里面的UISprite来自多个图集,就会有多个drawCall);2)当一个UISprite等UIWidget控件的active改变时,DrawCall会重新计算一遍;3)DrawCall的计算比较耗时,首先会收集DrawCall下所有UIWidget的verts,norms,trans,uvs,cols等信息,还要向GPU传输数据,相对比较耗时。
问题:当在一个Panel中有大量的UIWidget,有一部分是经常改变active的,有些部分则不怎么变。这样就会导致频繁地计算DrawCall。
性能提升方案:把经常改变active的控件单独到一个panel中。把不怎么变动的部分独立到另外的panel中。
49. NGUI中设置自定义的Shader,并改变properties的&#20540;。
使用UITexture,可以设置自定义的Material。需要注意的是,改变properties的&#20540;,需要使用UITexture中的drawCall的dynamicMaterial,如下:
UITexture tex=....;
tex.drawCall.dynamicMaterial.SetFloat(&_percent&, du/DU);
50. 鉴别unity中的InputTouch的类型:
public enum OperateStatus {
OS_Idle=0,
OS_Draging=1,
OS_Scaling=2
void Update () {
TouchPhase tp = Input.GetTouch(0).
if(tp==TouchPhase.Ended || tp==TouchPhase.Canceled) {
oStatus = OperateStatus.OS_I
oStatus = Input.touchCount&1 ? OperateStatus.OS_Scaling : OperateStatus.OS_D
51. 物理系统的性能提示:
Unity中涉及物理模拟的有静态碰撞(static collider)和动态碰撞(dynamic collider)。其中,静态碰撞物体是指具有collider属性但没有rigidbody的物体,动态碰撞物体是指具有collider和rigidbody属性的物体。
Unity引擎期待静态物体是静止,会把场景中的所有静态物体统一计算成一个叫static collider cache的东西来加速物理模拟。但是如果其中某个静态物体进行了移动,系统就要重新计算static collider cache;而这个代价是很大的。所以如果一个物体是要经常移动的,应该设计成动态物体,即添加rigidbody成分;至于使rigidbody不响应重力等作用力的影响,请参考rigidbody中的&Use Gravity&和“Is Kinematic”属性。
如果rigidbody是kinematic的,它不响应force,可以用来制作飞行障碍物等效果。如果不是kinematic的,就会响应foce。
参考如下官方教程最后的性能分析:
52. unity中四种Collider的对比:
类型能否移动是否接收Trigger消息是否受碰撞影响是否受物理影响Static代价大NoNoNoKinematic
RigidbodyYesYesNoNoCharacter ControllerYes未知YesNoRigidbodyYesYesYesYes
仔细观察上面的表&#26684;,发现越往下,特性越多。官方在介绍collision的时候,没有加入讨论Character Controller,这个是我自己添加比较的。
另外,如果一个物体能够受碰撞影响,当然就能够接收Collition消息了。
关于接收Collision消息和Trigger消息的官方的详细表,请参考 的文末的表&#26684;。53. 法线计算相关 使一个物体依附在一个表面上,保持物体的up方向和表面的法线方向一致(物体记为obj,表面记为suf):方法1:obj.transform.rotation = Quaternion.FromToRotation(obj.transform.up, suf.transform.up)* obj.transform.方法2:Vector3 axis = Vector3.Cross(obj.transform.up, suf.transform.up);
float angle = Vector3.Angle(obj.transform.up, suf.transform.up);
obj.transform.Rotate(axis, angle, Space.World);54. AngryBots中的showFPS代码:
@script ExecuteInEditMode
private var gui : GUIT
private var updateInterval = 1.0;
private var lastInterval : // Last interval end time
private var frames = 0; // Frames over current interval
function Start()
lastInterval = Time.realtimeSinceS
frames = 0;
function OnDisable ()
DestroyImmediate (gui.gameObject);
function Update()
#if !UNITY_FLASH
var timeNow = Time.realtimeSinceS
if (timeNow & lastInterval + updateInterval)
var go : GameObject = new GameObject(&FPS Display&, GUIText);
go.hideFlags = HideFlags.HideAndDontS
go.transform.position = Vector3(0,0,0);
gui = go.guiT
gui.pixelOffset = Vector2(5,55);
var fps : float = frames / (timeNow - lastInterval);
var ms : float = 1000.0f / Mathf.Max (fps, 0.00001);
gui.text = ms.ToString(&f1&) + &ms & + fps.ToString(&f2&) + &FPS&;
frames = 0;
lastInterval = timeN
55. 关于Hierarchy中按名称的字母排序功能:
public class AlphaNumericSort : BaseHierarchySort
public override int Compare(GameObject lhs, GameObject rhs)
if (lhs == rhs) return 0;
if (lhs == null) return -1;
if (rhs == null) return 1;
return EditorUtility.NaturalCompare(lhs.name, rhs.name);
}把上面的代码放到工程中之后,在Hierarchy中就会出现排序的按钮,如下:
BaseHierarchySort的官方地址:
56. 用脚本在Mac下打开多个Unity工程:
默认到情况下,Mac中只能够允许一个Unity实例。但是通过脚本(把工程路径写到Unity启动参数中)可以方便地打开多个常用的工程(参考:)
#!/bin/bash
/Applications/Unity/Unity.app/Contents/MacOS/Unity -projectPath &/path/to/your/projectA&57. 在Mac启动多个Unity实例:
a)设置Unity启动时显示对话框。具体做法=》确保如下选择打勾:Unity Perferences/General/Always Show Project W
b)从命令行运行unity,并提供参数(这里参数“11”是随意写的,作用是让Mac启动一个新的进程,末尾的“&”表示后台启动进程):
/Applications/Unity5/Unity5.app/Contents/MacOS/Unity 11 &c)写成脚本行形式(脚本名称为:startUnity5),方便启动:#!/bin/bash
/Applications/Unity5/Unity5.app/Contents/MacOS/Unity 11 &d)运行脚本当然首先要设置脚本为可运行的,命令如下:sudo chmod &#43;x startUnity5运行脚本:./startUnity5
相关文章:
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:359887次
积分:4358
积分:4358
排名:第5000名
原创:87篇
评论:194条
(2)(1)(2)(1)(1)(1)(2)(3)(1)(1)(1)(2)(3)(1)(1)(2)(1)(2)(2)(2)(2)(2)(9)(4)(9)(2)(2)(7)(4)(3)(7)(1)(1)(1)(1)(1)27460人阅读
Unity Shaders(57)
写在前面三个月以前,在的最后,我们说到在Surface Shader中实现描边效果的弊端,也就是只对表面平缓的模型有效。这是因为我们是依赖法线和视角的点乘结果来进行描边判断的,因此,对于那些平整的表面,它们的法线通常是一个常量或者会发生突变(例如立方体的每个面),这样就会导致最后的效果并非如我们所愿。如下图所示:因此,我们有一个更好的方法来实现描边效果,也就是通过两个pass进行渲染——首先渲染对象的背面,用黑色略微向外扩展一点,就是我们的描边效果;然后正常渲染正面即可。而我们应该知道,surface shader是不可以使用pass的。如果我们想要使用上述方法实现描边,我们就需要写另一种shader——fragment shader。和surface shader相比,这种shader需要我们编写更多的代码,处理更多的事情,但也可以让我们更加了解shader是如何工作的。而之前的也分析过,其实surface shader的背后也是生成了对应的vertex&fragment shader。这篇文章主要参考了Unity Gems里的一篇文章,但正如文章评论里所说,有些技术比如求attenuation稳重方法已经“过时”,因此本文会对这类问题以及一些作者没有说清的问题给予说明。在查资料的时候,发现由于Unity背后做了太多事,定义了很多变量、函数和宏,而又没有给出详尽的使用说明,写起来实在太头大了。。。同样,本篇内容仅供参考。Vertex & Fragment ShadersVertex & Fragment Shaders的工作流程如下图所示(简略版,来自):所以,看起来也没那么难啦~我们只需要编写两个函数就可以喽~我们来分析下它的流程。首先,vertex program收到系统传递给它的模型数据,然后把这些处理成我们后续需要的数据(但至少要包含这些顶点的位置信息)进行输出。其他的输出数据比如有,纹理的UV坐标以及其他需要传递给fragment program的数据。然后,系统对vertex program输出的顶点数据进行插值,并将插值结果传递给fragment program。最后,fragment program根据这些插值结果计算最后屏幕上的像素颜色。在本篇文章,我们首先会学习编写一个简单的diffuse & diffuse bumped shader。然后再来具体看如何编写一个具有多个passes的shader。Diffuse, Vertex Lit Fragment Shader开始的开始,我们首先需要在SubShader中使用Pass&{}关键字定义一个pass。一个Pass可以为该阶段定义一系列的tags。例如,我们可以剔除(Cull)背面或者正面,控制是否写入Z buffer等。我们的diffuser shader将会剔除背面。具体可见。下面是我们的Pass定义:
Tags { &LightMode& = &Vertex& }
Lighting OnCGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include &UnityCG.cginc&
// More code here
}在上面的代码里,我们定义了一个pass,设定LightMode为Vertex,告诉它打开光源并且剔除背面。然后,我们定义了CG程序的开头部分,指定了vertex和fragment programs的名字。最后,我们包含了Unity定义的一个文件,以便在后面的CG程序中可以使用某些函数和变量。LightMode是个非常重要的选项,因为它将决定该pass中光源的各变量的值。如果一个pass没有指定任何LightMode tag,那么我们就会得到上一个对象残留下来的光照值,这并不是我们想要的。其他各个LightMode的具体含义可以参见(很重要,一定要去看,特别是对于每个Pass的细节解释,一定要点进去看!!!),这里做一个简单的解释。LightMode=Vertex:会设置4个光源,并按亮度从明到暗进行排序,它们的值会存储在unity_LightColor[n], unity_LightPosition[n], unity_LightAtten[n]这些数组中。因此,[0]总会得到最亮的光源。LightMode=ForwardBase: _LightColor0将会是主要的directional light的颜色。LightMode=ForwardAdd:和上面一样, _LightColor0将是该逐像素光源的颜色。Vertex Lit是什么在我们写shader的时候有很多选择——我们可以定义多个passes,其中每一个pass处理一个光源,这样来处理所有的光源;或者我们选择逐顶点处理所有的光源(在一个pass里处理掉),然后再对它们进行插值。很明显,后面这种方式会快很多,因为它仅仅需要一个pass就可以了,而前一个方式需要更多的passes。如果我们写了一个Vertex Lit shader,那么我们就会按照第二种方式那样,一次考虑所有的光源对顶点的影响。如果我们写了一个多passes的shader,那么它就会被多次调用,每次针对一个光源,考虑该光源对模型的影响。对于Vertex Lit,Unity已经为我们编写了一些辅助函数,我们会在后面看到。The Vertex Program下面,我们正式开始编写代码。首先,我们需要定义vertex program。而它需要得到模型的相关信息作为输入,因此,我们定义下面的结构:
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};这个结构定义依赖某些语法,即那些“:XXX”样子的值。我们的变量叫什么并不重要,但这些“:XXX”语法则说明系统将使用哪些值去填充它们。这里,我们通过上述代码可以得到了model space中的顶点位置、法线方向以及纹理坐标。那么,在定义了vertex program的输入后,我们还需要定义它的输出。之前我们说过,vertex program的输出将会被插值用于生成像素,而这些插值后的值就是fragment program的输入。
struct v2f {
float4 pos : POSITION;
float2 uv : TEXCOORD0;
float3 color : TEXCOORD1;
};上面就是我们的输出。在这里,之前所说的语义就没有那么重要了——只有一个是必须的,即用POSITION标识的变量,这是把顶点坐标转换到clip space后的位置。我们输出的所有值(并且没有uniform限定词)都将在fragment program之前被插值。注意:但对于DX11和Xbox360来说,必须要有语义说明,否则会报错。即需要为变量指定TEXCOORD1等位置。出于性能的考虑,很显然我们应该尽可能在vertex function里进行更多的运算,这是因为vertex function是逐顶点调用的,而fragment function则是逐像素调用的。下面是真正的vertex function,它把输入a2v转换成输出v2f(也是fragment function的输入)。
v2f vert(a2v v) {
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.color = ShadeVertexLights(v.vertex, v.normal);
}第一行,我们定义了输出v2f的一个实例。然后把顶点的位置和Unity提前定义的一个矩阵UNITY_MATRIX_MVP(在UnityShaderVariables.cginc里定义)相乘,从而把顶点位置从model space转换到clip space。我们使用了矩阵乘法操作mul来执行这个步骤。第二行,我们为给定的纹理计算其uv坐标,即根据mesh上的uv坐标来计算真正的纹理上对应的位置。我们使用了Unity.CG.cginc中的宏TRANSFORM_TEX来实现。注意,要使用宏TRANSFORM_TEX,我们需要在shader中定义一些额外的变量,即必须定义一个名为_YourTextureName_ST (也就是你的纹理的名字加一个 _ST后缀)。这是因为宏TRANSFORM_TEX的定义为:#define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)。这是因为我们的纹理有Tiling和Offset参数,如下图中面板所示,因此需要对原mesh上的uv进行相应调整才能得到真正的纹理坐标。最后,我们计算得到顶点的初始颜色——即光源对该顶点的影响。在我们的第一个shader中,我们使用一个名为ShadeVertexLights的函数,它的输入为模型的顶点和法线。这是一个内置的函数,它将考虑4个距离最近(若距离相等则按光源类型排序)的光源以及一个环境光(在Edit-&Render Settings-&Ambient Light里设置)。它的实现可以在UnityCG.cginc里找到。其他辅助函数可以详见。The Fragment Shader根据上述过程,系统会在每个顶点上调用vertex program,并将其输出在同一个几何图元上进行插值。下面,我们根据这些插值后的值来得到对应的像素值。下面是真正的fragment program:
float4 frag(v2f i) : COLOR {
float4 c = tex2D(_MainTex, i.uv);
c.rgb = c.rgb * i.color * 2;
}上述代码使用了surface shader中也很常见的纹理采样操作,来得到对应的纹理像素值。然后,将该纹理颜色和插值后的vertex function输出的顶点光颜色进行相乘,并把结果乘以2(否则颜色会太暗。)。最后,返回得到的像素值。完整代码最后,完整的Vertex Lit Diffuse代码如下:Shader &Custom/VertexLit& {
Properties {
_MainTex (&Base (RGB)&, 2D) = &white& {}
SubShader {
Tags { &RenderType&=&Opaque& }
Tags { &LightMode& = &Vertex& }
Lighting On
#pragma vertex vert
#pragma fragment frag
#include &UnityCG.cginc&
sampler _MainT
float4 _MainTex_ST;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
struct v2f {
float4 pos : POSITION;
float2 uv : TEXCOORD0;
float3 color : TEXCOORD1;
v2f vert(a2v v) {
v2o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.color = ShadeVertexLights(v.vertex, v.normal);
float4 frag(v2f i) : COLOR {
float4 c = tex2D(_MainTex, i.uv);
c.rgb = c.rgb * i.color * 2;
FallBack &Diffuse&
}这样,我们就完成了第一个vertex & fragment shader。上述效果如果用surface shader可能只要几句话,但你渐渐会发现,虽然使用vertex & fragment shader会增加更多的代码量,但它能做的真是太多了!上述shader的效果如下(啦啦啦,又是小苹果+呆萌小怪兽的组合~~~):Diffuse Normal Map Shader下面我们要向shader添加一个非常常见的法线纹理(Normal Texture)。Normal Maps如果你在Unity里使用过法线纹理的话,你应该知道在使用之前,你需要先把该纹理的类型设置成Normal,对吧?那么,到底为什么要这样呢?法线纹理跟其他纹理有什么不一样呢?法线纹理具有以下性质它存储了模型表面的法线方向。有基于model space(肉眼看起来颜色比较丰富,有红色蓝色等)和基于tangent space(通常都是蓝色的)的两种法线纹理,而Unity常见的是后面一种法线纹理。由于法线向量中每一维的范围在(-1,1),因此我们需要把它重新映射到(0,255)。具体做法是把原值除以2再偏移0.5,最后乘以255。在存储的时候是压缩存储。因为法线纹理都是被正则化的,即是单位向量,模为1,所以实际上只需要存储该向量的两个维度就可以了,第三维可以用前两个推导出来。由于上一点,每一个维度占用16 bits,即每个rgba包含了两个维度的值。当使用法线纹理的时候,我们需要在tangent space中处理光照对模型的影响。也就是说,我们需要把和计算光照对像素的影响的数据都转换到tangent space中,然后在这个坐标系中计算得到最终的颜色。而且,在这里我们实际上是计算了逐像素的光照,而不是像前一个shader那样是逐顶点的。我们选择在tangent space计算光照是因为这种做法的计算量更少。我们只需要基于每个顶点,把光照信息(有时还需要观察点信息等)转换到tangent space,再对其进行插值即可。而另一种方式是在world&space中处理光照,这意味着我们需要把法线纹理中的每一个法线转换到world space中,因此我们需要基于每个像素进行处理。和逐顶点的处理方式相比,这种方法显然需要更多的计算。在Unity里转换到tangent space是比较容易的。下面,我们不会使用逐顶点的光照处理函数ShadeVertexLights,而是逐像素的处理光照。照亮我们的模型下面,我们将使用Lambert光照模型,也就是法线*光照方向*衰减*2。在我们把需要的数据都转换到tangent space后,处理光照就变得非常简单了。可以用下图(来源:Unity Gems)来演示这样一个过程:但是,光源在哪里呢?Unity为我们提供了那些对模型有影响的光源(按重要度排序,例如距离远近、光照类型等)的位置、颜色和衰减等信息。Unity使用了三个数据来定义顶点光源:unity_LightPosition,unity_LightAtten和unity_LightColor。例如[0]表示最重要的光源。当我们编写一个multi-pass的光照模型(正如我们下面写的那样)时,我们只需要一次处理一个单独的光源,这种情况下,Unity同样定义了一个名为_WorldSpaceLightPos0的值,来帮助我们得到它的位置,并且还提供了一个非常有用的函数ObjSpaceLightDir,它可以计算得到该光源的方向。而为了得到该光源的颜色,我们可以在程序中包含“Lighting.cginc”文件,然后使用_LightColor0进行访问。Forward Lighting(而非Vertex Lit)在第一个shader里我们使用了vertex lights,而现在,我们来看下怎么为光源定义多个passes。那么,开始吧!首先,我们需要更改Tags中的LightMode,让其值为ForwardBase,来让Unity我们设置光源数据。
Tags { &LightMode& = &ForwardBase& }然后,我们还需要添加#pragma指令:#pragma multi_compile_fwdbase这都是为了能让Unity各种内置数据、宏定义等可以正常工作。真的是很头大啊,至今官方也没有给出详细的参考资料。。。(Rant!!!)然后,为了使用法线纹理我们需要定义两个变量,一个是名为_XXX的sampler2D变量,一个是名为_XXX_ST的float4变量(当然你还需要在Properties中定义一个名为_XXX的新属性)。现在我们需要为vertex program定义新的输入:
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 tangent : TANGENT;
};这里我们添加了一个新的变量,其语义是:TANGENT。我们会在把光源方向转换到tangent space中时需要这个变量。Tangent Space转换为了把向量从object space转换到tangent space,我们需要为顶点定义另外两个向量。通常对一个顶点来说,我们知道它的法线normal,而其中一个向量tangent是和normal正交的,另一个向量binormal则是normal和tangent的叉乘结果。有了这三个向量,我们就可以定义一个矩阵来执行到tangent space的转换。幸运地是,UnityCG.cginc里定义了一个名为TANGENT_SPACE_ROTATION的宏,它提供了一个名为rotation的矩阵来把object space下的坐标转换到tangent space中。Vertex到Fragment Programs的输出在知道转换的方法后,我们需要在vertex function里计算tangent space下的光源方向,然后对其进行插值后传递给fragment function。因此,我们需要在vertex function的输出里添加新的变量——光源方向。
struct v2f {
float4 pos : POSITION;
float2 uv : TEXCOORD0;
float2 uv2 : TEXCOORD1;
float3 lightDirection : TEXCOORD2;
LIGHTING_COORDS(3,4)
};lightDirection将会存储插值后的光源方向向量。uv2将会存储法线纹理的纹理坐标。最后的LIGHTING_COORDS(3,4)是在AutoLight.cginc里定义的宏,它负责创建光源坐标,用于某些内置的光照计算。在下面计算光源的attenuation时,我们会需要这些值。该shader只对directional lights和point lights有效。本例中我们没有考虑spotlight的角度。The Vertex Program
v2f vert(a2v v) {
TANGENT_SPACE_ROTATION;
o.lightDirection = mul(rotation, ObjSpaceLightDir(v.vertex));
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv2 = TRANSFORM_TEX(v.texcoord, _BumpTex);
TRANSFER_VERTEX_TO_FRAGMENT(o);
}在vertex program里,我们使用了宏TANGENT_SPACE_ROTATION(在UnityCG.cginc里定义)来创建一个名为rotation的矩阵,并使用它把object space转换到tangent space中。为了让这个宏能够正确处理我们的输入,vertex program的输入必须是一个名为v的结构体,并且它包含了一个名为normal的法线以及一个名为tangent的切线。这都是因为它的宏定义里指明了变量的名字的缘故。然后,我们使用内置函数ObjSpaceLightDir(v.vertex)计算了在object space中光源(这时指的就是最重要的那个光源)的方向。随后,我们再把结果和新的rotation矩阵相乘,从而把方向从object space又转换到了tangent space。下面几行,我们计算得到顶点在clip space中的位置以及纹理的uv坐标。最后,我们使用了名为的TRANSFER_VERTEX_TO_FRAGMENT宏,它同样在AutoLight.cginc里定义,和上面v2f中的宏LIGHTING_COORDS协同工作,它会根据该pass处理的光源类型(spot?point?or directional?)来计算光源坐标的具体值,以及进行和shadow相关的计算等。Directional和Point LightsUnity把光源的位置存储在float4类型的_WorldSpaceLightPos0里,即_WorldSpaceLightPos0包含了4个元素。如果这个光源是directional,那么xyz就是这个光源的方向,而w(即最后一个元素)则是0;如果这时一个point light,那么xyz将表示光源的位置,而w则是1。那么,这些有什么影响呢?这其实方便了ObjSpaceLightDir函数的计算过程。它首先将顶点的位置乘以光源位置的w元素,然后再用光源位置减去顶点的位置,来得到光源方向。因此,如果是一个directional light,我们相乘后就会得到0,即返回光源的xyz值(实际上就是光源的方向);如果是一个point light,我们就会得到顶点到光源的一个方向向量。The Fragment Function
float4 frag(v2f i) : COLOR {
float4 c = tex2D(_MainTex, i.uv);
float3 n = UnpackNormal(tex2D(_BumpTex, i.uv2));
float3 lightColor = UNITY_LIGHTMODEL_AMBIENT.
float atten = LIGHT_ATTENUATION(i);
// Angle to the light
float diff = saturate(dot(n, normalize(i.lightDirection)));
lightColor += _LightColor0.rgb * (diff * atten);
c.rgb = lightColor * c.rgb * 2;
}在fragment function里,我们首先从法线纹理里解压出法线。然后,我们使用Unity设置的环境光作为初始颜色值。随后,我们计算了衰减值,即光源距离的远近。这里,我们同样使用了AutoLight.cginc里的宏,即LIGHT_ATTENUATION,它同样会判断该pass处理的光源类型,然后得到光源的衰减率。然后,我们把法线和光源方向进行点乘得到漫反射值,再和光源颜色以及衰减值结合起来,叠加到像素值上。为了得到光源的颜色,我们使用了_LightColor0——这需要我们在shader中包含“Lighting.cginc”文件。或者,我们也可以在shader中定义一个名为_LightColor0的变量,Unity会自行填充它的值。uniform float4 _LightColor0;完整代码最后完整的代码如下:Shader &Custom/DiffuseNormal& {
Properties {
_MainTex (&Base (RGB)&, 2D) = &white& {}
_BumpTex (&Bump Texture&, 2D) = &white& {}
SubShader {
Tags { &RenderType&=&Opaque& }
Tags { &LightMode& = &ForwardBase& }
Lighting On
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include &UnityCG.cginc&
#include &Lighting.cginc&
#include &AutoLight.cginc&
sampler _MainT
sampler _BumpT
float4 _MainTex_ST;
float4 _BumpTex_ST;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 tangent : TANGENT;
struct v2f {
float4 pos : POSITION;
float2 uv : TEXCOORD0;
float2 uv2 : TEXCOORD1;
float3 lightDirection : TEXCOORD2;
LIGHTING_COORDS(3,4)
v2f vert(a2v v) {
TANGENT_SPACE_ROTATION;
o.lightDirection = mul(rotation, ObjSpaceLightDir(v.vertex));
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv2 = TRANSFORM_TEX(v.texcoord, _BumpTex);
TRANSFER_VERTEX_TO_FRAGMENT(o);
float4 frag(v2f i) : COLOR {
float4 c = tex2D(_MainTex, i.uv);
float3 n = UnpackNormal(tex2D(_BumpTex, i.uv2));
float3 lightColor = UNITY_LIGHTMODEL_AMBIENT.
float atten = LIGHT_ATTENUATION(i);
// Angle to the light
float diff = saturate(dot(n, normalize(i.lightDirection)));
lightColor += _LightColor0.rgb * (diff * atten);
c.rgb = lightColor * c.rgb * 2;
FallBack &Diffuse&
Shader效果如下:在Forward Mode中处理Multiple Lights通过上面的学习,我们已经学会了如何处理一个光源,但仅仅是一个。要处理多光源,我们就需要编写另一个pass,并且使用新的tags来告诉Unity我们想要逐个处理光源。这基本上只需要两步:一个pass处理第一个光源,就像我们上面做的那样然后定义更多的pass,来处理后续的光源,并把结果添加(add on)到前面的结果上因此,我们把之前pass的代码再粘贴一遍,来创建一个新的pass,但要把tag改成:Tags { &LightMode& = &ForwardAdd& }并且更改#pragma指令:#pragma multi_compile_fwdadd然后添加一个新的命令来告诉Unity怎样混合前后两个pass的值:Blend One One然后,我们移除掉第二个pass对UNITY_LIGHTMODEL_AMBIENT的处理,因为我们已经在第一个pass中处理过这个值了。我们最后的代码如下:Shader &Custom/DiffuseNormal& {
Properties {
_MainTex (&Base (RGB)&, 2D) = &white& {}
_BumpTex (&Bump Texture&, 2D) = &white& {}
SubShader {
Tags { &RenderType&=&Opaque& }
Tags { &LightMode& = &ForwardBase& }
Lighting On
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include &UnityCG.cginc&
#include &Lighting.cginc&
#include &AutoLight.cginc&
sampler _MainT
sampler _BumpT
float4 _MainTex_ST;
float4 _BumpTex_ST;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 tangent : TANGENT;
struct v2f {
float4 pos : POSITION;
float2 uv : TEXCOORD0;
float2 uv2 : TEXCOORD1;
float3 lightDirection : TEXCOORD2;
LIGHTING_COORDS(3,4)
v2f vert(a2v v) {
TANGENT_SPACE_ROTATION;
o.lightDirection = mul(rotation, ObjSpaceLightDir(v.vertex));
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv2 = TRANSFORM_TEX(v.texcoord, _BumpTex);
TRANSFER_VERTEX_TO_FRAGMENT(o);
float4 frag(v2f i) : COLOR {
float4 c = tex2D(_MainTex, i.uv);
float3 n = UnpackNormal(tex2D(_BumpTex, i.uv2));
float3 lightColor = UNITY_LIGHTMODEL_AMBIENT.
float atten = LIGHT_ATTENUATION(i);
// Angle to the light
float diff = saturate(dot(n, normalize(i.lightDirection)));
lightColor += _LightColor0.rgb * (diff * atten);
c.rgb = lightColor * c.rgb * 2;
Tags { &LightMode& = &ForwardAdd& }
Lighting On
Blend One One
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdadd
#include &UnityCG.cginc&
#include &Lighting.cginc&
#include &AutoLight.cginc&
sampler _MainT
sampler _BumpT
float4 _MainTex_ST;
float4 _BumpTex_ST;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 tangent : TANGENT;
struct v2f {
float4 pos : POSITION;
float2 uv : TEXCOORD0;
float2 uv2 : TEXCOORD1;
float3 lightDirection : TEXCOORD2;
LIGHTING_COORDS(3,4)
v2f vert(a2v v) {
TANGENT_SPACE_ROTATION;
o.lightDirection = mul(rotation, ObjSpaceLightDir(v.vertex));
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv2 = TRANSFORM_TEX(v.texcoord, _BumpTex);
TRANSFER_VERTEX_TO_FRAGMENT(o);
float4 frag(v2f i) : COLOR {
float4 c = tex2D(_MainTex, i.uv);
float3 n = UnpackNormal(tex2D(_BumpTex, i.uv2));
float3 lightColor = float3(0);
float lengthSq = dot(i.lightDirection, i.lightDirection);
float atten = LIGHT_ATTENUATION(i);
// Angle to the light
float diff = saturate(dot(n, normalize(i.lightDirection)));
lightColor += _LightColor0.rgb * (diff * atten);
c.rgb = lightColor * c.rgb * 2;
FallBack &Diffuse&
我们在场景里放置两个光源——一个平行光,用于ForwardBase Pass的计算,一个Point Light,用于ForwardAdd Pass的计算。效果如下:写在最后本文里对处理光源attenuation的方法和里的方法不同,按原文里的做法在Unity 4.5(更早的版本不清楚)是无法得到正确的attenuation的,即把点光源拉进拉远不会对模型有任何影响,除非拉出了光源范围,这时会有一个不正常的明暗突变。为了找正确的方法真是麻烦啊。。。Unity关于shader的文档的确需要加强,而且在Unity里写Vertex & Fragment Shader绝对比想象中的难,有一条准则就是,如果它提供给里某些功能的函数(比如这里计算attenuation的方法,要4个步骤,#pragma&multi_compile_fwdadd&+&LIGHTING_COORDS&+&TRANSFER_VERTEX_TO_FRAGMENT+ LIGHT_ATTENUATION),那么千万不要自己尝试去写一个函数出来。。。某些内置的变量实在是不知道它们什么时候工作、怎么工作。。。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1395513次
积分:13061
积分:13061
排名:第697名
原创:114篇
转载:20篇
译文:21篇
评论:1937条
我叫乐乐,程序媛一枚,就读于上海交通大学软件学院,研究生,数字媒体方向。喜欢用计算机来绘制各种五彩缤纷的画面~欢迎访问我的和 :)
邮件:lelefeng1992 # gmail DOT com
PS:为防止垃圾邮件,请自行转换为正确格式哦~
---------------------
阅读:54884
阅读:26354
文章:45篇
阅读:548846
(2)(1)(3)(1)(2)(2)(1)(1)(2)(2)(4)(2)(3)(6)(3)(8)(5)(6)(3)(6)(5)(7)(1)(2)(8)(1)(4)(5)(4)(3)(15)(2)(3)(27)(5)}

我要回帖

更多关于 unity matrix mvp作用 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信