ZFXEngine开发笔记之Omni Light

作者:i_dovelemon

日期:2014 / 9 / 5

来源:CSDN博客

主题:Per-pixel lighting, Omni light, early-z, Multi-pass, Assembly Shader

引言

在第一人称射击游戏中,经常需要点光源来完成光照计算。所以,这篇文章中将会讲述如何创建这个点光源,并且如何设计让引擎可以支持多光源渲染。

Omni light介绍

所谓的Omni light,就是指点光源。不过在这里,我们进行点光源的光照计算的时候,没有用到顶点的法线来进行光照计算。Omni light具有两个属性,分别是在世界坐标空间中的位置position,以及在光源的照射半径radious。有了这两个属性,我们就可以对顶点进行光照计算了。

由于在下面,我将使用Assembly Shader的方式来进行Shader的编写,并且,我们需要根据Omni light的两个属性,来设置顶点的衰变参数。衰变参数指的是,在距离光源越近的时候,顶点被照射的越亮,距离越远的时候越暗,乃至于超出了Omni light设定的光照半径之后,就不在进行光照。

所以,我们需要一个矩阵,一个能够将顶点与Omni light的衰变参数计算出来的矩阵。

首先,为了计算的方便,我先假设,物体模型的世界变换矩阵是单位矩阵。那么就是说,Omni light的位置坐标和模型上的顶点坐标可以认为在一个空间中,不需要进行坐标变换了。(注:如果模型的世界变换坐标并不是单位矩阵的话,就需要进行变换)

然后,我将Omni light的光源位置平移到世界坐标的原点处,用同样的变换矩阵对顶点进行变换的话,那么顶点的坐标就表示了在各个轴上距离光源的距离。

在有了距离之后,我们需要进行一些变换,将衰变参数控制在0-1之间,在0-1之间能够方便的对光源进行颜色计算,并且在Pixel Shader中,所有的输入数据都要在0-1之间,就算不是这个区间的值,也会被强制的缩减到0-1之间。

下面是进行此种变换的代码:

<span style="font-family:Microsoft YaHei;">ZFXMatrix CalcOmniAttenuMatrix(ZFXVector pos, float radious)
{
	ZFXMatrix mT ;
	mT.identity();
	mT.translate(-pos.x, -pos.y, -pos.z);

	ZFXMatrix mS ;
	mS.identity();
	float invRadious = 0.5f / radious ;
	mS._11 = mS._22 = mS._33 = invRadious ;

	ZFXMatrix mB ;
	mB.identity();
	mB.translate(0.5f, 0.5f, 0.5f);

	mT = mT * mS ;
	mT = mT * mB ;

	ZFXMatrix mResult ;
	mResult.transposeOf(mT);

	return mResult ;
}// end for CalcOmniAttenuMatrix</span>

上面的代码,先进行平移操作,也就是mT的操作,这个操作会将顶点平移,然后进行缩放操作mS。这里对缩放操作的缩放因子为0.5 / radious。读者可以想象一下,radious是光源的光照半径,缩放时,对顶点的三个分量,比如x/raidous * 0.5,也就是说如果顶点在X方向的半径范围内,那么这个缩放后的值就会变成了-0.5-0.5之间的值了。在加上最后的一个平移0.5的操作,那么就变成了0-1.0的范围了。这样,刚好满足Pixel
Shader的输入条件。

注意,在最后,我么将计算出来的矩阵,进行了转置操作,这是因为在Assembly Shader中,进行向量和矩阵的乘法运算时,Assembly Shader的计算方式,是按照向量和矩阵的行相乘,而不是数学上的和列相乘。这样做的原因是进行行相乘的寻址更加简单。如果按列相乘的话,那么还要跨越寄存器去取值,就会给计算带来额外的开销。读者可能会想,c20不就是一个向量的单位吗?怎么能容下一个矩阵了?这是GPU的处理机制,如果c20容纳不下,那么就会使用c21,c22,c23来继续容纳。知道容纳下为止。也就是说,c20,c21,c22,c23这四个寄存器保存了一个矩阵的数据。

下面就是这个光源的Vertex Shader和Pixel Shader的代码:

<span style="font-family:Microsoft YaHei;">vs.1.1
dcl_position0 v0
dcl_normal0   v3
dcl_texcoord0 v6
m4x4 oPos, v0, c0
mov oT0, v6
m4x4 oT1, v0, c20
</span>
<span style="font-family:Microsoft YaHei;">ps.1.1
tex t0
texcoord t1
dp3_sat r0, t1_bx2, t1_bx2
mul r0, c1, 1-r0
mul r0, r0, t0</span>

在前面一篇文章中已经讲述了如何使用GPU中的各种寄存器。这里不再赘述。

上面的Vertex Shader,除了对顶点进行基本的变换操作之外,就是使用上面计算出来的矩阵对顶点进行变换,并且将结果保存在oT1寄存器中,供Pixel Shader使用。

在Pixel Shader中,使用texcoord指令将t1中的数据取出,而不是tex指令。tex指令是使用t0中的坐标来对纹理进行采样,并且把采样后的纹素值保存在t0中。在有了t1这个值,也就是各个顶点三个轴距离Omni light的距离之后,使用了如下的指令:

dp3_sat r0, t1_bx2, t1_bx2

这条指令中的t1_bx2是将t1中的值减去0.5,然后再乘以2。在前面说过,Pixel Shader中的输入寄存器中的值只能是0-1。所以,经过这样的变换之后,就变成了-1到1的范围,然后使用dp3指令,计算出这个坐标距离原点(也就是Omni light)的距离的平方,dp3_sat会增加一个操作,那就是将dp3的计算结果clamp到0-1这个范围来。

这样,我们就有了一个平方衰变参数。进行衰变时,一般很少使用线性的衰变方法,加上平方之后,衰变的效果更加的好。有了衰变参数之后,我们用1-r0,那么就能够得到光源的光照强度了。衰变参数越低,光照强度就越高,不是吗?c1中存放了光源的颜色属性。使用衰变参数来获取颜色,并且与Diffuse Texture进行混合,就会得到最后的像素值。保存在r0中作为输出像素。

Early-Z介绍

Early-Z技术,是为了能够让Pixel Shader执行比较复杂的运算,而不降低效率而创建出来的。Early-Z检测会在Pixel Shader之前进行,它会先将无法通过Z测试的数据剔除掉,从而节省进行Pixel Shader计算的开销。传统的Z-Test是在Pixel Shader之后,进行的。如果只有这个的话,那么就没有办法在Pixel Shader之前,剔除掉数据,Pixel Shader就需要对很多在接下来Z-Test中被剔除的数据进行计算,实在是浪费资源。所以Early-Z技术能够提高Pixel
Shader的效率。但是Ealry-Z是硬件的隐式特许。也就是说,DirectX API没有什么函数能够指定它是运行还是不运行。默认没有开启Alpha blend的情况下,它就是开启的。这是因为Ealry-Z技术和Alpha Blend技术有冲突。

Multiply Light and Multiply-Pass

实现多光源渲染的方法有很多。这里使用的是一种Multiply-pass渲染技术。也就是说,每一次,对一种光源渲染一次场景,然后将渲染的结果与前面一次渲染的结果进行Alpha Blend,从而实现多光源渲染的效果。

在引擎中设置了一个m_bAdditionBlend的参数,这个参数就是控制是否开启Multi-pass渲染。如果你需要对此渲染的话,那么就需要将这个变量设置为true,引擎会在最终渲染的时候,根据这个变量来设置Alpha blend,从而来进行Alpha Blend。

下面是进行设置的函数:

<span style="font-family:Microsoft YaHei;">void ZFXD3D::useAdditiveBlending(bool b)
{
	if(m_bAdditive == b)
		return ;

	//clear all vertex cache
	m_pVertexMan->forcedFlushAll();
	m_pVertexMan->invalidateStates();

	m_bAdditive = b ;
	if(!m_bAdditive)
	{
		m_pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
		m_pDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);
		m_pDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
	}
}// end for useAdditiveBlending

bool ZFXD3D::useAdditiveBlending()
{
    return m_bAdditive ;
}// end for useAdditiveBlending
</span>

一旦取消的时候,就关闭Alpha Blend。启动之后,不做任何事,因为在最终的渲染代码中有如下一段代码:

<span style="font-family:Microsoft YaHei;">//should we use additive blending
if(m_pZFXD3D->useAdditiveBlending())
{
	m_pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
	m_pDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
	m_pDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
}</span>

通过这样的方法,就能够实现多次的渲染,从而实现多光源。

程序截图:

今天的笔记到此结束!

时间: 2024-10-08 10:28:24

ZFXEngine开发笔记之Omni Light的相关文章

ZFXEngine开发笔记-SSE版本的ZFXVector

SSE介绍 在学习3D游戏编程大师技巧的时候,就了解到,可是使用一种称之为"单指令,多数据(SIMD)"的技术来编写3D数学库.通过这样的方法,可以将我们经常使用的诸如向量计算,矩阵变换等操作加快很多倍.这次,在学习3D引擎开发的时候,也用到了这个技术.SIMD是一种技术的名称,而并不是具体的工具.实现这种技术,不同的CPU厂商推出了不同的技术,像MMX, 3DNow!, SSE, SSE2, SSE3....由于我的计算机上使用的是Intel的处理器,它支持MMX,SSE,SSE2,

ZFXEngine开发笔记之Bump Mapping(2)

作者:i_dovelemon 日期: 2014 / 9 / 13 来源 : CSDN 主题 :Bump Mapping, Tangent Space, Normal Map, Height Map 引言 在上篇文章中,我讲述了如何根据高度图来创建法线图.并且承诺在后面会讲述3D几何学中的重要工具Tangent Space的相关知识.今天,就在这里,向大家讲述如何接下来的工作部分. Tangent Space 我们知道,在前篇文章中,讲述的法线图中的法线是在纹理图的空间中也就是Tangent Sp

ZFXEngine开发笔记之3D模型格式支持(1)

作者:i_dovelemon 来源:CSDN 日期:2014 / 9 / 17 主题:3D Format, Milk 3D Shape, Chunk Based System, Skeleton Animation (文中以红色中字标示的文字,是整个文章的注意项,请读者留心) 引言 在3D游戏领域,艺术家们通过3D建模软件建立自己的艺术品模型和绚丽的3D场景.当我们想要在自己的游戏中使用这些模型的时候,我们就需要将这种模型变成能够被我们的引擎所识别的文件格式.每一个建模软件都有自己的文件格式,不

ZFXEngine开发笔记之Shadow Volume

作者:i_dovelemon 来源:CSDN 日期:2014 / 10 / 20 主题: Shadow Volume 引言 游戏中,往往有很多的光影效果.想要营造出很好的光影效果,物体在光源照射下的阴影就必不可少.本节内容就向大家讲述如何构建阴影. Shadow Volume 构建阴影的方法有很多,常用的两种方法是Shadow Volume和Shadow mapping.本片博文将向大家讲述使用Shadow Volume来构建阴影的方法. Shadow Volume的算法是由Frank Crow

ZFXEngine开发笔记- GPU硬件架构

作者:i_dovelemon 日期:2014 / 8 / 31 来源: CSDN博客 文章: GPU硬件架构 引言 在3D图形学中,可编程渲染管道的出现,无疑是一种创举.在下面的文章中,会向大家简要的介绍现如今的可编程渲染管道中最重要的Vertex Shader和Pixel Shader的硬件架构以及如何使用汇编语言来编写Shader. Vertex Shader 在硬件上,Vertex Shader中所有的运算都在一个名为vertex arithmetic logic unit(ALU)中进行

ZFXEngine开发笔记之Bump Mapping(1)

作者:i_dovelemon 日期:2014 / 9 / 7 来源:CSDN博客 主题:Bump Mapping, Tangent Space, Normal Map, Height Map 引言 我们知道,在真实世界中,任何物体的表面都不是非常光滑的,这种粗糙的表面效果,在光源的照射下会有明暗的感觉.那么,如何在3D图形中模拟这种效果了?是通过建模的方法,为模型的表面建造出粗糙的表面?这种方法很难做出真实的粗糙感出来,毕竟表面的粗糙程度是很细微的效果,很难用建模的方式建模出来.所以,只能用其他

张高兴的 Windows 10 IoT 开发笔记:BH1750FVI 光照度传感器

原文:张高兴的 Windows 10 IoT 开发笔记:BH1750FVI 光照度传感器 BH1750FVI 是一款 IIC 接口的数字型光强度传感器集成电路.下面介绍一下其在 Windows 10 IoT Core 环境下的用法. 项目运行在 Raspberry Pi 2/3 上,使用 C# 进行编码. 1. 准备 包含 BH1750FVI 的传感器,这里选择的是淘宝上最多的 GY-30:Raspberry Pi 2/3 一块,环境为 Windows 10 IoT Core:公母头杜邦线 4-

DLNA&UPnP开发笔记(4)— PlatinumKit库介绍

前面几篇文章主要从理论上介绍了DLNA和UPnP协议的相关概念和知识点,本文主要介绍一下PlatinumKit库,该库实现了UPnP协议栈,代码写得非常漂亮,但文档不多,所以希望我的介绍和引导能够帮助初学者更快速地掌握该库的应用. 1. PlatinumKit库的官方网站 http://www.plutinosoft.com/platinum/ 2. PlatinumKit库的特点 (1)由C++编写 (2)支持Windows, Mac OSX, Linux, iPhone, Android (

Android开发笔记(一百一十九)工具栏ToolBar

Toolbar 在前面的博文<Android开发笔记(二十)顶部导航栏>中,我们学习了ActionBar的用法,可是ActionBar着实是不怎么好用,比如文字风格不能定制.图标不能定制,而且还存在低版本的兼容性问题,所以实际开发中大家还是不倾向使用ActionBar.为此,Android提供了加强版的工具栏控件即Toolbar,因为Toolbar继承自ViewGroup,而且可在布局文件中像其它布局视图一样使用,所以灵活性大大的提高了.既然Android都与时俱进了,那我们也不能落后,现在就