unity如何渲染3D角色

2015年01月19日 15:09 0 点赞 0 评论 更新于 2025-11-21 14:49

如果你还不清楚在Unity中如何渲染3D角色,不必着急。本文将详细介绍如何利用Shader来渲染游戏中的3D角色,以及如何使用Unity提供的Surface Shader编写自定义Shader。

一、从Shader开始

1. 创建默认Shader

通过Assets -> Create -> Shader创建一个默认的Shader,并将其命名为MyShader

2. 查看默认Shader代码

打开MyShader,可以看到Unity默认的Shader代码:

Shader "Custom/MyShader" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Lambert
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}

3. 应用Shader查看效果

将该Shader赋给一个角色,就能看到该Shader所能呈现的Diffuse渲染效果。

4. 准备编写自定义Shader

接下来,我们将以这个默认Shader为基础,编写自定义的Shader。该Shader所用到的参数,将在下一章节进行详细说明。

二、实现多种自定义渲染效果

1. BumpMap效果

若要实现Bump Map效果,可对上述Shader进行如下修改:

1.1 在属性Properties中添加内容

Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_BumpMap("Bumpmap", 2D) = "bump" {}
}

1.2 在SubShader的变量中进行相应修改

sampler2D _MainTex;
sampler2D _BumpMap;
struct Input {
float2 uv_MainTex;
float2 uv_BumpMap;
};

1.3 修改surf函数,加入对Normal分量的计算

void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
}

这样,角色的材质部分会变为相应形式(暂定BumpMap的Shader名为MyShader1)。然后,根据Base图创建其Normal Map图,并将其拖入到BumpMap中即可看到BumpMap的效果。

详细说明

  • Shader名称Shader "Custom/MyShader1" 这种表示方式表明了该Shader在编辑器中的显示位置。
  • Properties:可通过name ("displayname", property type) = default value语义进行声明。
  • “name” 是与Shader脚本中对应的名字。
  • “display name” 是在材质视图中所显示的名字。
  • “propertytype” 是指该property的类型,常见类型有RangeColor2DRectCubeFloatVector
  • “defaultvalue” 是指该property的默认值。需要注意的是,若在Properties中加入新的属性,需在CGPROGRAM中的SubShader中加入同样名字的参数。
  • LOD:这里的“LOD”主要指Shader的LOD程度,即对于超出该范围的物体将不再通过该Shader进行渲染。
  • _BumpMap和uv_BumpMap_BumpMap 用于关联Properties中的_BumpMap属性,uv_BumpMap 用于获取BumpMap图中的uv坐标。
  • surf函数:在surf函数中获取每个顶点的纹理信息以及法线信息,这些信息将应用于后续的Vertex Fragment和Pixel Fragment。tex2D函数可以读取纹理_MainTexIN.uv_MainTex坐标位置的像素颜色值。AlbedoAlpha分别获取该像素的RGB值和Alpha值,其中“Albedo”是一个漫反射参数,表示一个表面的漫反射能力,即一个表面上出射光强与入射光强的比值,具体介绍可见:http://en.wikipedia.org/wiki/Albedo

2. Blinn - Phong效果

若要实现Blinn - Phong效果,可对上述Shader进行如下修改:

2.1 在属性Properties中添加内容

_AmbientColor ("Ambient Color", Color) = (0.1, 0.1, 0.1, 1.0)
_SpecularColor ("Specular Color", Color) = (0.12, 0.31, 0.47, 1.0)
_Glossiness ("Gloss", Range(1.0,512.0)) = 80.0

2.2 在SubShader的变量中加入相应修改

fixed4 _AmbientColor;
fixed4 _SpecularColor;
half _Glossiness;

2.3 修改surf函数

fixed4 c = tex2D (_MainTex, IN.uv_MainTex);

将原有的half4替换为fixed4,是为了提高渲染性能。因为fixed的精度较half低,更高的精度意味着更大的计算量,而这里fixed的精度已足够,所以使用fixed替代half4,以降低计算消耗,增加渲染性能。

2.4 修改编译指令并添加光照计算函数

#pragma surface surf Lambert改成#pragma surface surf CustomBlinnPhong,同时加入与其对应的LightingCustomBlinnPhong函数来计算顶点光照:

inline fixed4 LightingCustomBlinnPhong (SurfaceOutput s, fixed3 lightDir, fixed3 viewDir, fixed atten)
{
fixed3 ambient = s.Albedo * _AmbientColor.rgb;
fixed NdotL = saturate(dot (s.Normal, lightDir));
fixed3 diffuse = s.Albedo * _LightColor0.rgb * NdotL;
fixed3 h = normalize (lightDir + viewDir);
float nh = saturate(dot (s.Normal, h));
float specPower = pow (nh, _Glossiness);
fixed3 specular = _LightColor0.rgb * specPower * _SpecularColor.rgb;
fixed4 c;
c.rgb = (ambient + diffuse + specular) * (atten * 2);
c.a = s.Alpha + (_LightColor0.a * _SpecularColor.a * specPower * atten);
return c;
}

该函数名称前需加入“Lighting”关键字,因为虽然它由#pragma surface surf CustomBlinnPhong调用,但为了让Unity能识别出这是一个自定义的光照函数,必须遵循此规则。通过以上设置,角色的材质部分会变为相应形式(暂定该Shader名为MyShader2)。

3. 边缘光照(Rim Light)和卡通渲染(Toon Shading)

可以通过对上述Shader做以下改进来实现这种效果:

3.1 在属性Properties中添加内容

_RimColor ("Rim Color", Color) = (0.12, 0.31, 0.47, 1.0)
_RimPower ("Rim Power", Range(0.5, 8.0)) = 3.0
_Ramp ("Shading Ramp", 2D) = "gray" {}

3.2 在SubShader的变量中加入相应修改

sampler2D _MainTex;
sampler2D _BumpMap;
sampler2D _Ramp;
fixed4 _AmbientColor;
fixed4 _SpecularColor;
half _Glossiness;
fixed4 _RimColor;
half _RimPower;
struct Input {
float2 uv_MainTex;
float2 uv_BumpMap;
half3 viewDir;
};

3.3 修改surf函数

void surf (Input IN, inout SurfaceOutput o) {
fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
fixed rim = 1.0 - saturate (dot (normalize(IN.viewDir), o.Normal));
o.Emission = (_RimColor.rgb * pow (rim, _RimPower));
}

这里主要用于计算边缘光照,首先通过视线与法线的夹角找到模型的边缘,然后根据距离的远近来控制发射光的强度。

3.4 修改编译指令并调整光照计算

#pragma surface surf CustomBlinnPhong改成#pragma surface surf CustomBlinnPhong exclude_path:prepass,同时在LightingCustomBlinnPhong函数中修改漫反射光的计算:

fixed NdotL = saturate(dot (s.Normal, lightDir));
fixed diff = NdotL * 0.5 + 0.5;
fixed3 ramp = tex2D (_Ramp, float2(diff, diff)).rgb;
fixed diffuse = s.Albedo * _LightColor0.rgb * ramp;

通过以上设置,角色的材质部分会变为相应形式(暂定该Shader名为MyShader3),可以看到边缘光照的效果,同时还能看到明显的明暗变化的卡通渲染效果。

三、小结

综上所述,本文介绍了人物的几种基本渲染方法及其Shader实现。这里没有深入分析每种渲染效果的原理,而是从实际出发,直接给出对应的简单实现方法。如果想要深入理解光照模型,可以通过Google搜索其原理进行学习。最后,给出各种渲染方法的对比图如下:(此处原文未给出对比图,可后续补充)

作者信息

feifeila

feifeila

共发布了 3994 篇文章