3D游戏引擎系列六

3D引擎 同时被 3 个专栏收录
154 篇文章 6 订阅
97 篇文章 8 订阅
13 篇文章 31 订阅

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。

CSDN课程视频网址:http://edu.csdn.net/lecturer/144

Cel Shading是卡通渲染一种,卡通渲染尤其是在手机游戏中应用非常广泛。由于手机硬件的限制,美工为了优化资源,节约模型的面数,通常设计场景和角色时,一般都设计成卡通效果。程序员通过GPU编程对材质进行渲染,要实现卡通渲染首先要了解其实现原理,其核心思想主要是对要进行卡通渲染的对象进行边界绘制。卡通游戏种类很多,比如2头身比例,3头身比例等,3D卡通模型的渲染主要是通过材质表现,这样在模型面数相对减少的情况下并不影响其表现效果,所以非常适用于移动端游戏开发。卡通设计效果举例如下图:


实现方式:新建一个文本文件,将其扩展名字改成.fx。Shader完整内容如下:

float4x4 matWorldViewPrj;
float4x4 matWorld;
float3 lightPos = float3(0,60,-60);
float lightAttenuation = 0.01;

float3 eyePos = float3(0,200,400);

float4 mtlDiffuseColor = float4(1,1,1,1);

texture texCartoon;
texture texCartoonEdge;

//--
struct VS_INPUT
{
	float3 pos : POSITION;
	float3 normal : NORMAL;
};

struct VS_OUTPUT
{
	float4 pos : POSITION;
	float3 worldPos : TEXCOORD0;
	float3 normal : TEXCOORD1;
};

VS_OUTPUT my_vs(VS_INPUT vert)
{
	VS_OUTPUT vsout;
	
	vsout.pos = mul(float4(vert.pos,1),matWorldViewPrj);
	vsout.worldPos = mul(float4(vert.pos,1),matWorld);
	vsout.normal = normalize(mul(vert.normal, matWorld));
	
	return vsout;
}

sampler CartoonSampler = sampler_state
{
	Texture = <texCartoon>;
	MinFilter = Point;
	MagFilter = Point;
	MipFilter = None;
	AddressU  = Clamp;
	AddressV  = Clamp;
};

sampler CartoonEdgeSampler = sampler_state
{
	Texture = <texCartoonEdge>;
	MinFilter = Point;
	MagFilter = Point;
	MipFilter = None;
	AddressU  = Clamp;
	AddressV  = Clamp;
};

//--
float4 my_ps(float3 worldPos : TEXCOORD0,
			 float3 normal : TEXCOORD1) : COLOR
{
	float4 color;
	
	//-- ambient
	float lum = 0.2f;
	
	//-- diffuse
	normal = normalize(normal);
	
	float3 L = lightPos - worldPos;
	float d = length(L);
	L /= d; // normalize L
	
	float atten = 1/(lightAttenuation*d);
	float diff = saturate(dot(normal, L));
	
	lum += diff * atten;
	
	float4 cartoonColorDiff = tex2D(CartoonSampler,lum);
	
	color = cartoonColorDiff*mtlDiffuseColor;
	
	//-- edge
	float3 V = normalize(eyePos - worldPos);
	float e = (dot(normal,V));
	float4 edgeColor = tex2D(CartoonEdgeSampler,e);
	
	color *= edgeColor;
	
	return color;
}

//--
technique my_tech
{
	pass p0
	{
		VertexShader = compile vs_1_1 my_vs();
		PixelShader = compile ps_2_0 my_ps();
	}
}

针对上述代码解释一下,变量声明:

texture texCartoon;

表示的是输入卡通化的纹理

texture texCartoonEdge;

表示的是输入的卡通边缘的纹理,其他变量声明上节已经介绍过了,这里就不重复了。

sampler CartoonSampler = sampler_state;
表示的是纹理采样,为了让大家更容易理解,结合着Unity的纹理设置。效果如下图:

下面是卡通纹理采样和纹理边缘取样,这两个是最重要的实现,它们调用的是Shader的接口函数tex2D实现的。

float4 cartoonColorDiff = tex2D(CartoonSampler,lum);
float4 edgeColor = tex2D(CartoonEdgeSampler,e);

卡通纹理的像素着色器是在如下函数实现的:

float4 my_ps(float3 worldPos : TEXCOORD0, float3 normal : TEXCOORD1) : COLOR

下面的代码片段是用物体上的材质法线和灯光的照射方向进行点乘计算,从而得到物体上的颜色。

float4 color;
	
	//-- ambient
	float lum = 0.2f;
	
	//-- diffuse
	normal = normalize(normal);
	
	float3 L = lightPos - worldPos;
	float d = length(L);
	L /= d; // normalize L
	
	float atten = 1/(lightAttenuation*d);
	float diff = saturate(dot(normal, L));
	
	lum += diff * atten;
	
	float4 cartoonColorDiff = tex2D(CartoonSampler,lum);
	
	color = cartoonColorDiff*mtlDiffuseColor;

物体边缘渲染是通过法线计算得到的,最后使用纹理取样函数tex2D得到的最终的物体边缘颜色,代码片段如下所示:


float3 V = normalize(eyePos - worldPos);
	float e = (dot(normal,V));
	float4 edgeColor = tex2D(CartoonEdgeSampler,e);
	
	color *= edgeColor;

卡通渲染需要两张贴图:一张是卡通纹理,另一张是边缘纹理。如下图:


卡通边缘纹理如下图:

这两张贴图在加载Shader文件之前都会被程序事先读取到内存里,Shader文件加载上节已经写过了,在这里就不重复了,直接上图片文件的加载,使用的是Direct3D的库函数代码如下:

HRESULT hr = D3DXCreateTextureFromFile(pD3DDevice,"Media\\Cartoon.jpg",&m_pCartoonTex);
	hr = D3DXCreateTextureFromFile(pD3DDevice,"Media\\edge.jpg",&m_pCartoonEdgeTex);

文件加载使用的是Direct3D提供的接口:D3DXCreateTextureFromFile函数,接下来把Shader文件需要的参数通过C++代码传递给Shader文件,最后在GPU中执行。函数核心代码如下所示:

float t = timeGetTime()/4000.0f;
	float r = 150;
	m_lightPos.x = sinf(t) * r;
	m_lightPos.z = cosf(t) * r;
	m_lightPos.y = sinf(t) * r;;

	hr = m_pEffect->SetFloatArray("lightPos",(float*)&m_lightPos,3);
	hr = m_pEffect->SetFloatArray("eyePos",(float*)&g_camera.getEyePos(),3);
	hr = m_pEffect->SetTexture("texCartoon",m_pCartoonTex);
	hr = m_pEffect->SetTexture("texCartoonEdge",m_pCartoonEdgeTex);
	
	D3DXMATRIX matWorld;
	D3DXMatrixIdentity(&matWorld);

	D3DXMATRIX matWorldViewProj = matWorld * g_camera.getViewMat() * g_camera.getProjectMat();
	hr = m_pEffect->SetMatrix("matWorldViewPrj",&matWorldViewProj);
	hr = m_pEffect->SetMatrix("matWorld",&matWorld);

其中参数m_lightPos与上节的用法一样,表示灯光可以在场景中旋转运动。使用C++的接口SetFloatArray函数把Shader脚本的参数传递进来,再使用SetTexture函数把图片传递给Shader脚本文件用于纹理采样。这个与Unity中的纹理渲染一样,只是Unity编辑器提供了供用户直接拖放的接口,最后使用SetMatrix函数接口把3D世界空间中的矩阵传递给GPU去计算。实现的最终效果图:


在光线正面照射情况下,物体的材质颜色是明亮的,背面是比较暗的,这个是通过物体表面法线与灯光的夹角计算得到的。

敬请期待。。。。。。。。
  • 1
    点赞
  • 0
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值