提:
之前的shader无法满足在应用期间高亮显示(或其他shader特效),需要通过复制一份需要高亮的模型,为这部分模型添加高亮Shader或修改之前模型材质
1
.
问题
1
:增加模型面数浪费性能,
1
.
解决办法:用同一份模型,通过传参,改变Shader的渲染方式。
2
.
步骤:
1
.
开发Shader替换原先Shader,给美术同事。
2
.
通过代码,统一控制需要高亮的模型高亮
2
.
问题
2
:每次替换新模型的时候,都需要美术在开发的电脑上操作,浪费大量时间
1
.
解决办法:美术人员,只做一套模型,开发拿来就用
2
.
步骤:
1
.
技术美术把相关shader给美术,让美术直接替换
优化方向
-
通过一个脚本参数来控制其高亮与正常Shader的切换
-
美术提供的模型与材质与这个功能解耦:当需要替换模型改动材质时,对我实现这个功能不会增加工作量
假设需求
美术给我一个机器人,我要快速的将机器人的左右手脚依次点亮
如何做
-
找到单独存放手脚的 父物体 ,逐一遍历出来它下面的所有物体——遍历出需要添加材质的物体
-
为其(子物体及子物体的子物体)动态添加材质球-(发光材质球)。
-
将遍历与添加功能合并起来,
-
对添加功能的对象记录下来,便于将这些物体的发光材质关闭和再次添加
-
将核心代码封装成方法就可以随便用了
-
如用到Timeline里
实现时需要掌握的知识点
找到单独存放手脚的父物体,逐一遍历出来它下面的所有物体
使用 Transform.GetComponentsInChildren<>() 的方法来获取指定物体下的所有子、孙物体,并复制给数组,然后进行遍历
GetComponentsInChildren<>()
Transform
[
]
father
=
GetComponentsInChildren
<
Transform
>
(
)
;
foreach
(
var
child
in
father
)
{
Debug
.
Log
(
child
.
name
)
;
}
//transform.GetComponentsInChildren<Transform>(true); //获取全部子物体,无论SetActive是否为true
// 想要父物体下所有有材质的对象
//MeshRenderer[] father = obj.GetComponentsInChildren<MeshRenderer>();
为其 物体 动态添加发光材质球。
GetComponent<MeshRenderer>().materials = myMaterial; //将赋值好的新材质或数组可以直接付给MRend
动态为MeshRenderer添加新材质
public
Material
[
]
mMaterial
;
//创建一个材质数组
public
MeshRenderer
rend
;
//创建有MRend实例
void
Start
(
)
{
rend
=
GetComponent
<
MeshRenderer
>
(
)
;
//声明让MRend实例等于这个对象自身的MRend 为了获取其身上的数据
mMaterial
=
new
Material
[
rend
.
materials
.
Length
+
1
]
;
//声明new材质数组等于原MRend的材质数量+1 +1是为了给高亮材质留着的
}
void
Update
(
)
{
if
(
Input
.
GetKeyDown
(
KeyCode
.
A
)
)
//注意,这只是测试代码,多点A会超出索引
{
for
(
int
i
=
0
;
i
<
rend
.
materials
.
Length
+
1
;
i
++
)
//在这里为new材质数组具体赋值
{
if
(
i
<
rend
.
materials
.
Length
)
{
mMaterial
[
i
]
=
GetComponent
<
MeshRenderer
>
(
)
.
materials
[
i
]
;
//让new材质没有超过原材质数量的数组等于原本的材质
}
else
mMaterial
[
i
]
=
Resources
.
Load
(
"FerBox23new"
)
as
Material
;
//让超出原来数量的数组等于新的材质球
}
this
.
GetComponent
<
MeshRenderer
>
(
)
.
materials
=
mMaterial
;
//将赋值好的材质数组直接付给MRend
}
Debug
.
Log
(
rend
.
materials
.
Length
.
ToString
(
)
)
;
}
也可以动态替换第一个材质,动态删除任何一个材质,动态改变任何材质的贴图或属性。
其他补充
//动态替换第一个材质
public
Material
mMaterial
;
public
MeshRenderer
rend
;
void
Start
(
)
{
mMaterial
=
Resources
.
Load
(
"FerBox23new"
)
as
Material
;
//替换的材质存放的地址
rend
=
GetComponent
<
MeshRenderer
>
(
)
;
rend
.
material
=
mMaterial
;
Debug
.
Log
(
rend
.
materials
.
Length
.
ToString
(
)
)
;
}
//动态删除任何一个材质
Destroy
(
this
.
GetComponent
<
MeshRenderer
>
(
)
.
materials
[
1
]
)
;
动态改变任何材质的贴图或属性。
将遍历与添加功能合并起来
将遍历与添加功能合并起来 过程
public
GameObject
obj
;
void
Update
(
)
{
if
(
Input
.
GetKeyDown
(
KeyCode
.
C
)
)
{
MeshRenderer
[
]
father
=
obj
.
GetComponentsInChildren
<
MeshRenderer
>
(
)
;
foreach
(
var
child
in
father
)
{
Material
[
]
mMaterial
;
MeshRenderer
rend
;
rend
=
child
.
GetComponent
<
MeshRenderer
>
(
)
;
mMaterial
=
new
Material
[
rend
.
materials
.
Length
+
1
]
;
for
(
int
i
=
0
;
i
<
rend
.
materials
.
Length
+
1
;
i
++
)
{
if
(
i
<
rend
.
materials
.
Length
)
{
mMaterial
[
i
]
=
child
.
GetComponent
<
MeshRenderer
>
(
)
.
materials
[
i
]
;
}
else
mMaterial
[
i
]
=
Resources
.
Load
(
"FerBox23new"
)
as
Material
;
}
child
.
GetComponent
<
MeshRenderer
>
(
)
.
materials
=
mMaterial
;
}
}
}
对添加功能的对象记录下来,便于将这些物体的发光材质关闭和再次添加
需增加一个脚本SonLightSwitch
GetObj脚本用于找到需要的物体,并为其初始化高亮功能 SonLightSwitch 负责记录本物体已被高亮功能初始化,和携带属性和方法方便GetObj调用
补充完整代码:加入添加与删除材质球功能,与防多填操作
GetObj
using
UnityEngine
;
/// <summary>
/// 找物体并对物体进行操作 父
/// </summary>
public
class
GetObj
:
MonoBehaviour
{
public
GameObject
obj
;
void
Update
(
)
{
if
(
Input
.
GetKeyDown
(
KeyCode
.
C
)
)
{
//限定Obj,将选择物体下的所有子孙物体中带MRend的存起来。
MeshRenderer
[
]
father
=
obj
.
GetComponentsInChildren
<
MeshRenderer
>
(
)
;
foreach
(
var
child
in
father
)
{
//如果当前物体没添加脚本,添加材质球,后添加脚本。
if
(
!
child
.
GetComponent
<
SonLightSwitch
>
(
)
)
{
Material
[
]
mMaterial
;
MeshRenderer
rend
;
rend
=
child
.
GetComponent
<
MeshRenderer
>
(
)
;
mMaterial
=
new
Material
[
rend
.
materials
.
Length
+
1
]
;
for
(
int
i
=
0
;
i
<
rend
.
materials
.
Length
+
1
;
i
++
)
{
if
(
i
<
rend
.
materials
.
Length
)
{
mMaterial
[
i
]
=
child
.
GetComponent
<
MeshRenderer
>
(
)
.
materials
[
i
]
;
}
else
mMaterial
[
i
]
=
Resources
.
Load
(
"FerBox23new"
)
as
Material
;
}
child
.
GetComponent
<
MeshRenderer
>
(
)
.
materials
=
mMaterial
;
child
.
gameObject
.
AddComponent
<
SonLightSwitch
>
(
)
;
child
.
GetComponent
<
SonLightSwitch
>
(
)
.
isAddLight
=
true
;
}
//如果当前物体添加了脚本,并且新的发光材质球被销毁了,重新为材质位挂上新材质
else
if
(
child
.
GetComponent
<
SonLightSwitch
>
(
)
.
isAddLight
==
false
)
{
child
.
GetComponent
<
SonLightSwitch
>
(
)
.
AddLightMat
(
)
;
}
}
}
if
(
Input
.
GetKeyDown
(
KeyCode
.
V
)
)
{
MeshRenderer
[
]
father
=
obj
.
GetComponentsInChildren
<
MeshRenderer
>
(
)
;
foreach
(
var
child
in
father
)
{
//对已经添加材质的物体,进行销毁发光材质球处理
if
(
child
.
GetComponent
<
SonLightSwitch
>
(
)
)
{
child
.
GetComponent
<
SonLightSwitch
>
(
)
.
DestroyLightMat
(
)
;
}
}
}
}
}
SonLightSwitch
using
UnityEngine
;
/// <summary>
/// 从属,被GetObj挂在被选物体上,用于记录操作和装载方法
/// </summary>
public
class
SonLightSwitch
:
MonoBehaviour
{
Material
[
]
mMaterial
;
public
bool
isAddLight
;
public
int
materialsIndex
=
0
;
/// <summary>
/// 被GetObj 创建的脚本,当脚本被挂在物体上时候,物体已经被添加了新的发光材质
/// 这个脚本只需要控制所在物体的新增材质的开关就好
/// </summary>
void
Start
(
)
{
materialsIndex
=
GetComponent
<
MeshRenderer
>
(
)
.
materials
.
Length
;
mMaterial
=
new
Material
[
materialsIndex
]
;
}
//为删除的材质位置重新添加发光材质
public
void
AddLightMat
(
)
{
for
(
int
i
=
0
;
i
<
materialsIndex
;
i
++
)
{
if
(
i
<
materialsIndex
-
1
)
{
mMaterial
[
i
]
=
GetComponent
<
MeshRenderer
>
(
)
.
materials
[
i
]
;
}
else
mMaterial
[
i
]
=
Resources
.
Load
(
"FerBox23new"
)
as
Material
;
}
GetComponent
<
MeshRenderer
>
(
)
.
materials
=
mMaterial
;
isAddLight
=
true
;
}
//将新添加的发光材质删除
public
void
DestroyLightMat
(
)
{
Destroy
(
this
.
GetComponent
<
MeshRenderer
>
(
)
.
materials
[
materialsIndex
-
1
]
)
;
isAddLight
=
false
;
}
}
将核心代码封装成方法就可以随便用了
using
UnityEngine
;
/// <summary>
/// 找物体并对物体进行操作 父
/// </summary>
public
class
GetObj
:
MonoBehaviour
{
public
GameObject
obj
;
void
Update
(
)
{
if
(
Input
.
GetKeyDown
(
KeyCode
.
C
)
)
{
//限定Obj,将选择物体下的所有子孙物体中带MRend的存起来。
MeshRenderer
[
]
father
=
obj
.
GetComponentsInChildren
<
MeshRenderer
>
(
)
;
foreach
(
var
child
in
father
)
{
······
}
}
if
(
Input
.
GetKeyDown
(
KeyCode
.
V
)
)
{
·······
}
}
························································
public
void
OpenObjs
(
GameObject
obj
)
//新生成某一个父类下面的所有物体新材质
{
MeshRenderer
[
]
father
=
obj
.
GetComponentsInChildren
<
MeshRenderer
>
(
)
;
foreach
(
var
child
in
father
)
{
//如果当前物体没添加脚本,添加材质球,后添加脚本。
if
(
!
child
.
GetComponent
<
SonLightSwitch
>
(
)
)
{
Material
[
]
mMaterial
;
MeshRenderer
rend
;
rend
=
child
.
GetComponent
<
MeshRenderer
>
(
)
;
mMaterial
=
new
Material
[
rend
.
materials
.
Length
+
1
]
;
for
(
int
i
=
0
;
i
<
rend
.
materials
.
Length
+
1
;
i
++
)
{
if
(
i
<
rend
.
materials
.
Length
)
{
mMaterial
[
i
]
=
child
.
GetComponent
<
MeshRenderer
>
(
)
.
materials
[
i
]
;
}
else
mMaterial
[
i
]
=
Resources
.
Load
(
"FerBox23new"
)
as
Material
;
}
child
.
GetComponent
<
MeshRenderer
>
(
)
.
materials
=
mMaterial
;
child
.
gameObject
.
AddComponent
<
SonLightSwitch
>
(
)
;
child
.
GetComponent
<
SonLightSwitch
>
(
)
.
isAddLight
=
true
;
}
//如果当前物体添加了脚本,并且新的发光材质球被销毁了,重新为材质位挂上新材质
else
if
(
child
.
GetComponent
<
SonLightSwitch
>
(
)
.
isAddLight
==
false
)
{
child
.
GetComponent
<
SonLightSwitch
>
(
)
.
AddLightMat
(
)
;
}
}
}
public
void
DestroyObj
(
GameObject
obj
)
//销毁这个物体下的新材质
{
MeshRenderer
[
]
father
=
obj
.
GetComponentsInChildren
<
MeshRenderer
>
(
)
;
foreach
(
var
child
in
father
)
{
//对已经添加材质的物体,进行销毁发光材质球处理
if
(
child
.
GetComponent
<
SonLightSwitch
>
(
)
)
{
child
.
GetComponent
<
SonLightSwitch
>
(
)
.
DestroyLightMat
(
)
;
}
}
}
}
如用到Timeline里
Signal Track可以来完成
信号轨道,意思就是在特定时间想外部发出一些信号,让外部去处理它,是一个Timeline和外部联系的一个特别好的渠道
有这三部分组成,project里的信号资产,Timeline里的信号发射位置,Scene里的信号包裹
project里的信号资产 可以直接创建
Scene里的信号包裹
Signal 轨道上的物体身上需要挂载SignalReceiver组件的
Timeline里的信号发射位置
在轨道上添加信号的方法
而其他的轨道上也可以添加Signal信号帧来向外界发射事件,让外界对其作出反应
展示
还可以做什么
-
将核心代码与鼠标的射线规定,鼠标射线返回它指的物体地址。将地址传给核心代码(obj)。 让鼠标只哪 哪亮
-
示教类指示玩家的交互形式
待解决问题
目前当关闭高亮时候,是将添加的材质球销毁了,但MRend里还保留着这个位置。不透明材质看不出来什么问题,原本不透明材质当销毁高亮材质后,留下的MRend空位就会出现洋红色。
高亮闪烁Shader代码
Shader
"Unlit/LightShader"
{
Properties
{
_MainTex
(
"MainTex"
,
2D
)
=
"white"
{
}
_MainColor
(
"MainColor"
,
Color
)
=
(
1
,
1
,
1
,
1
)
_Emiss
(
"Emiss"
,
Float
)
=
1.0
_RimPower
(
"RimPower"
,
Float
)
=
1.0
_Lerp
(
"Lerp"
,
Vector
)
=
(
1
,
0
.
5
,
0
,
0
)
_TiemS
(
"TiemS"
,
Float
)
=
1
}
SubShader
{
Tags
{
"Queue"
=
"Transparent"
}
Pass
{
ZWrite off
Blend SrcAlpha One
CGPROGRAM
#
pragma
vertex vert
#
pragma
fragment frag
#include
"UnityCG.cginc"
struct
appdata
{
float4
vertex
:
POSITION
;
float2
uv
:
TEXCOORD0
;
float3
normal
:
NORMAL
;
}
;
struct
v2f
{
float4
pos
:
SV_POSITION
;
float2
uv
:
TEXCOORD0
;
float3
normal_world
:
TEXCOORD1
;
float3
view_dir
:
TEXCOORD2
;
}
;
sampler2D
_MainTex
;
float4
_MainColor
;
float
_Emiss
;
float
_RimPower
;
float2
_Lerp
;
float
_TiemS
;
v2f
vert
(
appdata
v
)
{
v2f
o
;
o
.
pos
=
UnityObjectToClipPos
(
v
.
vertex
)
;
o
.
normal_world
=
normalize
(
mul
(
float4
(
v
.
normal
,
0.0
)
,
unity_WorldToObject
)
.
xyz
)
;
float3
pos_world
=
mul
(
unity_ObjectToWorld
,
v
.
vertex
)
.
xyz
;
o
.
view_dir
=
normalize
(
_WorldSpaceCameraPos
.
xyz
-
pos_world
)
;
o
.
uv
=
v
.
uv
;
return
o
;
}
float4
frag
(
v2f
i
)
:
SV_Target
{
float3
normal
=
normalize
(
i
.
normal_world
)
;
float3
view_dir
=
normalize
(
i
.
view_dir
)
;
float
NdotV
=
saturate
(
dot
(
normal
,
view_dir
)
)
;
float3
col
=
_MainColor
.
xyz
*
_Emiss
;
float
fresnel
=
pow
(
1.0
-
NdotV
,
_RimPower
)
;
float
mulTime20
=
_Time
.
y
*
_TiemS
;
float
temp_output_1_0_g5
=
mulTime20
;
float
lerpResult22
=
lerp
(
_Lerp
.
x
,
_Lerp
.
y
,
(
(
abs
(
(
(
temp_output_1_0_g5
-
floor
(
(
temp_output_1_0_g5
+
0.5
)
)
)
*
2
)
)
*
2
)
-
1.0
)
)
;
float
alpha
=
saturate
(
lerpResult22
*
fresnel
*
_Emiss
)
;
return
float4
(
col
,
alpha
)
;
}
ENDCG
}
}
}