如何利用Shader来渲染游戏中的3D角色
杨航最近在学Unity3D
本文主要介绍一下如何利用Shader来渲染游戏中的3D角色,以及如何利用Unity提供的Surface Shader来书写自定义Shader。
一、从Shader开始
1、通过Assets->Create->Shader来创建一个默认的Shader,并取名“MyShader”。
Unity3D教程:3D角色的渲染
2、将MyShader打开即可看见Unity默认的Shader代码
01 |
Shader "Custom/MyShader" { |
02 |
Properties { |
03 |
_MainTex ("Base (RGB)", 2D) = "white" {} |
04 |
} |
05 |
SubShader { |
06 |
Tags { "RenderType"="Opaque" } |
07 |
LOD 200 |
08 |
CGPROGRAM |
09 |
#pragma surface surf Lambert |
10 |
sampler2D _MainTex; |
11 |
struct Input { |
12 |
float2 uv_MainTex; |
13 |
}; |
14 |
void surf (Input IN, inout SurfaceOutput o) { |
15 |
half4 c = tex2D (_MainTex, IN.uv_MainTex); |
16 |
o.Albedo = c.rgb; |
17 |
o.Alpha = c.a; |
18 |
} |
19 |
ENDCG |
20 |
} |
21 |
FallBack "Diffuse" |
22 |
} |
3、将该Shader赋给一个角色,就可以看到该Shader所能表达出的Diffuse渲染效果。
Unity3D教程:3D角色的渲染
4、接来我们将以此默认Shader作为蓝本,编写出自定义的Shader。另外,该Shader所用到的参数,我们将在下一章节进行说明。
二、实现多种自定义渲染效果
1、 BumpMap效果
如果想实现Bump Map效果,可对上述的Shader做如下修改:
1.1 在属性Properties中加入:
1 |
Properties { |
2 |
_MainTex ("Base (RGB)", 2D) = "white" {} |
3 |
_BumpMap("Bumpmap", 2D) = "bump" {} |
4 |
} |
1.2 在SubShader的变量中也进行相应修改:
1 |
sampler2D _MainTex; |
2 |
sampler2D _BumpMap; |
3 |
struct Input { |
4 |
float2 uv_MainTex; |
5 |
float2 uv_BumpMap; |
6 |
}; |
1.3 最后修改surf函数,加入对Normal分量的计算:
1 |
void surf (Input IN, inout SurfaceOutput o) { |
2 |
"white-space: pre;"> half4 c = tex2D (_MainTex, IN.uv_MainTex); |
3 |
o.Albedo = c.rgb; |
4 |
o.Alpha = c.a; |
5 |
o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap)); |
6 |
} |
这样,角色的材质部分即可变为如下形式(暂定BumpMap的Shader名为“MyShader1”):
Unity3D教程:3D角色的渲染
然后,根据Base图来创建其Normal Map图,并拖入到BumpMap中即可。BumpMap的效果显示如下:
Unity3D教程:3D角色的渲染
说明:
(1)首先是title的解释
1 |
Shader "Custom/MyShader1" |
这种表示表明了该Shader在编辑器中的显示位置,例如我们可在如下地方找到该Shader。
Unity3D教程:3D角色的渲染
(2)其次是Properties
1 |
Properties { |
2 |
_MainTex ("Base (RGB)", 2D) = "white" {} |
3 |
_BumpMap("Bumpmap", 2D) = "bump" {} |
4 |
} |
Properties可通过如下语义进行声明:
name ("displayname", property type) = default value
“name” 是与Shader脚本中对应的名字
“display name”是在材质视图中所显示的名字
“propertytype”是指该property的类型,一般可有如下几种类型:Range,Color,2D,Rect,Cube,Float和Vector
“defaultvalue”是指该property的默认值
这里需要注意的是,如果你在Properties中加入了新的属性,那么你需要在CGPROGRAM中的SubShader中加入同样名字的参数。
(3)接下来是“LOD”语义词的解释。
这里的“LOD”主要是指Shader的LOD程度,即对于超出该范围的物体将不再通过该Shader进行渲染,具体的Shader LOD说明可以参见:Unity3D翻译——Shader Level of Detail
(4)我们在SubShader中还加入了
1 |
sampler2D _BumpMap; |
2 |
float2 uv_BumpMap; |
其中,_BumpMap是为了关联Properties中的_BumpMap属性。
而uv_BumpMap,是为了获取BumpMap图中的uv坐标。
(5)最后,我们在surf函数中获取每个顶点的纹理信息以及法线信息,这些信息将被应用于接下来的Vertex Fragment和Pixel Fragment。
1 |
void surf (Input IN, inout SurfaceOutput o) { |
2 |
half4 c = tex2D (_MainTex, IN.uv_MainTex); |
3 |
o.Albedo = c.rgb; |
4 |
o.Alpha = c.a; |
5 |
o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap)); |
6 |
} |
其中,tex2D函数可以读取纹理_MainTex中的IN.uv_MainTex坐标位置的像素颜色值。
Albedo和Alpha分别获取该像素的RGB值和Alpha值,其中“Albedo”是一个漫反射参数,它表示一个表面的漫反射能力,即一个表面上出射光强与入射光强的比值。具体介绍可见:http://en.wikipedia.org/wiki/Albedo。
2、 Blinn-Phong效果
如果想实现Blinn-Phong效果,可对上述的Shader做如下修改:
2.1 在属性Properties中加入:
1 |
_AmbientColor ("Ambient Color", Color) = (0.1, 0.1, 0.1, 1.0) |
2 |
_SpecularColor ("Specular Color", Color) = (0.12, 0.31, 0.47, 1.0) |
3 |
_Glossiness ("Gloss", Range(1.0,512.0)) = 80.0 |
2.2 在SubShader的变量中也加入相应修改:
1 |
fixed4 _AmbientColor; |
2 |
fixed4 _SpecularColor; |
3 |
half _Glossiness; |
2.3 最后修改surf函数,进行如下修改:
1 |
fixed4 c = tex2D (_MainTex, IN.uv_MainTex); |
这里将原有的half4替换为fixed4,这样做是为了提高渲染的性能,因为fixed的精度较之half要低,更高的精度意味着更大的计算量,而这里fixed的精度已经足够,所以使用fixed替代half4,从而来降低计算消耗,增加渲染性能。
2.4 将“#pragma surface surf Lamber”改成“#pragma surfacesurf CustomBlinnPhong”,同时加入与其对应的LightingCustomBlinnPhong函数来计算顶点光照。
01 |
inline fixed4 LightingCustomBlinnPhong (SurfaceOutput s, fixed3 lightDir, fixed3 viewDir, fixed atten) |
02 |
{ |
03 |
fixed3 ambient = s.Albedo * _AmbientColor.rgb; |
04 |
|
05 |
fixed NdotL = saturate(dot (s.Normal, lightDir)); |
06 |
fixed3 diffuse = s.Albedo * _LightColor0.rgb * NdotL; |
07 |
|
08 |
fixed3 h = normalize (lightDir + viewDir); |
09 |
float nh = saturate(dot (s.Normal, h)); |
10 |
float specPower = pow (nh, _Glossiness); |
11 |
fixed3 specular = _LightColor0.rgb * specPower * _SpecularColor.rgb; |
12 |
|
13 |
fixed4 c; |
14 |
c.rgb = (ambient + diffuse + specular) * (atten * 2); |
15 |
c.a = s.Alpha + (_LightColor0.a * _SpecularColor.a * specPower * atten); |
16 |
return c; |
17 |
} |
该函数的名称为什么不是“CustomBlinnPhong”呢?这是因为该函数虽然是由“#pragma surface surf CustomBlinnPhong”来调用,但是为了让该函数可以正常工作,我们需要在其名称前加入“Lighting”关键字,这样Unity才能识别出这是一个自定义的光照函数。
通过以上设置,角色的材质部分即可变为如下形式(暂定该Shader名为“MyShader2”):
Unity3D教程:3D角色的渲染
其显示效果如下:
Unity3D教程:3D角色的渲染
3、 边缘光照(Rim Light)和卡通渲染(Toon Shading)
可以通过对上述Shader做以下改进,来达到这种效果:
3.1 在属性Properties中加入:
1 |
_RimColor ("Rim Color", Color) = (0.12, 0.31, 0.47, 1.0) |
2 |
_RimPower ("Rim Power", Range(0.5, 8.0)) = 3.0 |
3 |
_Ramp ("Shading Ramp", 2D) = "gray" {} |
3.2 在SubShader的变量中也加入相应修改:
01 |
sampler2D _MainTex; |
02 |
sampler2D _BumpMap; |
03 |
sampler2D _Ramp; |
04 |
|
05 |
fixed4 _AmbientColor; |
06 |
fixed4 _SpecularColor; |
07 |
half _Glossiness; |
08 |
|
09 |
fixed4 _RimColor; |
10 |
half _RimPower; |
11 |
|
12 |
struct Input { |
13 |
float2 uv_MainTex; |
14 |
float2 uv_BumpMap; |
15 |
half3 viewDir; |
16 |
}; |
3.3 修改surf函数,进行如下修改:
1 |
void surf (Input IN, inout SurfaceOutput o) { |
2 |
fixed4 c = tex2D (_MainTex, IN.uv_MainTex); |
3 |
o.Albedo = c.rgb; |
4 |
o.Alpha = c.a; |
5 |
o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap)); |
6 |
fixed rim = 1.0 - saturate (dot (normalize(IN.viewDir), o.Normal)); |
7 |
o.Emission = (_RimColor.rgb * pow (rim, _RimPower)); |
8 |
} |
这里主要是用来计算边缘光照的,首先通过视线与法线的夹角来找到模型的边缘,然后再根据距离的远近来控制发射光的强度。
3.4 将“#pragma surface surf CustomBlinnPhong”改成“#pragma surfacesurf CustomBlinnPhong exclude_path:prepass”,同时在LightingCustomBlinnPhong函数来修改漫反射光的计算,来达到卡通渲染的效果。
1 |
fixed NdotL = saturate(dot (s.Normal, lightDir)); |
2 |
fixed diff = NdotL * 0.5 + 0.5; |
3 |
fixed3 ramp = tex2D (_Ramp, float2(diff, diff)).rgb; |
4 |
fixed diffuse = s.Albedo * LightColor0.rgb * ramp; |
通过以上设置,角色的材质部分即可变为如下形式(暂定该Shader名为“MyShader3”):
Unity3D教程:3D角色的渲染
其显示效果如下:
Unity3D教程:3D角色的渲染
可以看出边缘光照的效果,同时还可以看出明显的明暗变化的卡通渲染效果。
三、 小结
综上所述,本文已经给出了人物的几种基本渲染方法及其Shader实现,在这里我并没有去分析每种渲染效果的原理,而仅是从实际出发,直接给出对应的简单实现方法。如果想要对光照模型进行深入理解,可以Google搜索其原理进行了解。最后,给出各种渲染方法的对比图,显示如下:
Unity3D教程:3D角色的渲染
本系列文章由 Unity公司开发支持工程师Amazonzx 编写,
如何利用Shader来渲染游戏中的3D角色相关推荐
- DirectX游戏开发之3D角色动起(下)
DirectX游戏开发之3D角色动起(下) 直接先上图吧! 动作idle 动作attack 动作walk 动作run 看,多动作的模型搞下来了.原则上只要在此基础上略做修改就可以实现3d游戏的基本制作 ...
- 利用OLAMI在unity游戏中加入中文语音控制(一)
(欢迎转载.本文源地址:http://blog.csdn.net/speeds3/article/details/76209152) 最近打算尝试一下OLAMI在游戏中应用的可能性,这里做一下记录. ...
- [YTU]_2635(P4 游戏中的Human角色)
题目描述 在一个平面打斗游戏中,任何的角色(Role)都有血量(blood)和位置loc(此处loc是Location类的实例)属性.有了Role类,可以派生出不同的角色,如人.神仙.怪兽等.如下程序 ...
- 1996.游戏中的弱角色的数量
难度:中等 目录 一.问题描述 二.思想 1.解题思想 三.解题 1.代码实现 2.时间复杂度 and 空间复杂度 四.总结 一.问题描述 这里直接采用的是LeetCode上面的问题描述. 你正在参加 ...
- 游戏脚本在移动游戏设计中的作用_关卡设计师谈游戏中的敌对角色设计Monster Design...
原作者:Emil Glans 关卡设计是一门非常广泛的学科,不仅涵盖了基础几何学.脚本中的事件&敌人设计,还涉及到不同部分的组合.紧张度调节和叙事.在本文中,我将重点讨论一些与核心游戏设计密切 ...
- Unity3D游戏制作(二)——如何渲染3D角色
本系列文章由 Amazonzx 编写,欢迎转载,转载请注明出处. http://blog.csdn.net/amazonzx/article/details/7935341 本文主要介绍一下如何利用S ...
- 游戏中的网络同步机制(二) 王者荣耀对帧同步的应用
转载自:https://www.jianshu.com/p/81050871cce7 参考 解密:腾讯如何打造一款实时对战手游 从<王者荣耀>来聊聊游戏的帧同步 <王者荣耀>技 ...
- 从游戏中学习产品设计2:消费篇
上一期,我们介绍了游戏中的诱导充值套路,没有看的朋友可点击 从游戏中学习产品设计1:充值篇!了解游戏中是如何引诱玩家充值的,今天我们来聊一聊游戏中的消费套路,上文介绍了游戏中的三类货币:金币,钱币和时 ...
- MIT教授: 世界就是《黑客帝国》,人类在模拟游戏中扮演角色
转载于 新智元 编辑:元子 MIT教授认为我们目前有50%-100%的概率生活在<黑客帝国>那样的计算机模拟世界中,所谓现实可能不过是一种幻觉.而距离我们创建自己的模拟世界,最多需 ...
最新文章
- Why am I getting this error “Expected resource of type raw” in Android Studio?
- 查找linux所有目录中包含字符,Linux查找目录下包含有某字符串的全部文件
- 并发编程——线程——理论知识
- Int与String之间相互转换
- 年纪都这么大了,还在倒班工作......
- p2000专业软件测试对比,对比说说丽台p2000和p2200对比哪个好些?有何区别呢?良心点评实际情况...
- 探测距离机器人模型:通过超声波测距控制舵机转向,LED灯,语音播放,蜂鸣器(米思齐mixly,arduino)
- Revit二次开发The symbol is not active
- Python——KMeans(k均值聚类)实战(附详细代码与注解)
- 「leetcode」700. 二叉搜索树中的搜索:【递归法】【迭代法】详解
- 利用神经网络来计算XOR
- eclipse ADT完整环境下载
- QPainter基本绘图【1】
- 机器学习(Machine Learning)大家与资源
- JavaFX: Alert 弹窗
- ESP32 开发笔记(四)LVGL控件学习 ColorPicker 颜色选择器控件
- 基于FPGA数字时钟的设计(附源码)
- Win10笔记本用雷电3接口外接显卡加速tensorflow深度学习步骤
- 拼多多一晚被薅千万,倒赚276亿:一次蓄意营销的阴谋?
- 一文读懂,CPU、精简指令集、复杂指令集该如何理解?
热门文章
- openMVG跑自定义数据出错
- Java——反射(Class.forName()读取配置文件举例).
- Java——匿名内部类实现线程的两种方式
- LCD显示实验----STM32f4--HAL
- 计蒜客可以做计算机编程吗,如果你的编程能力不足以支撑你成为工程师的野心,不妨到计蒜客上学学看...
- uva 10570——Meeting with Aliens
- 分布式是写出来的(一)
- 处理大并发之二 对epoll的理解,epoll客户端服务端代码
- 【计算机系统设计】实践笔记(3)改进数据通路:jr指令分析与实现
- 【微信小程序】使用Hystrix的插件机制