DirectX光影详解

这里我用DirectX9实现一遍逐片段光照与阴影贴图,阴影贴图的算法与OpenGL的相同,各位仔细看好了.

首先声明四个着色器并获取它们的常量句柄:

void initShaders() {
	storeVertexShader=new Shader("../shader/store_vs.fx",SHADER_TYPE_VS);
	modelMatrixStoreHand=storeVertexShader->constTable->GetConstantByName(NULL,"modelMatrix");
	viewMatrixStoreHand=storeVertexShader->constTable->GetConstantByName(NULL,"viewMatrix");
	projMatrixStoreHand=storeVertexShader->constTable->GetConstantByName(NULL,"projMatrix");

	storePixShader=new Shader("../shader/store_ps.fx",SHADER_TYPE_PS);

	texVertexShader=new Shader("../shader/tex_vs.fx",SHADER_TYPE_VS);
	modelMatrixHand=texVertexShader->constTable->GetConstantByName(NULL,"modelMatrix");
	viewMatrixHand=texVertexShader->constTable->GetConstantByName(NULL,"viewMatrix");
	projMatrixHand=texVertexShader->constTable->GetConstantByName(NULL,"projMatrix");
	normalMatrixHand=texVertexShader->constTable->GetConstantByName(NULL,"normalMatrix");
	shadowMatrixHand=texVertexShader->constTable->GetConstantByName(NULL,"shadowMatrix");

	texPixShader=new Shader("../shader/tex_ps.fx",SHADER_TYPE_PS);
	ambHand=texPixShader->constTable->GetConstantByName(NULL,"amb");
	diffHand=texPixShader->constTable->GetConstantByName(NULL,"diff");
	lightDirHand=texPixShader->constTable->GetConstantByName(NULL,"lightDir");
	ambMtlHand=texPixShader->constTable->GetConstantByName(NULL,"ambMtl");
	diffMtlHand=texPixShader->constTable->GetConstantByName(NULL,"diffMtl");
}

store开头的顶点与片段着色器在渲染阴影贴图时使用,用于保存以光源位置为视点的场景深度,接着是取得mvp变换矩阵的句柄.

tex开头的顶点与片段着色器在渲染场景时使用,用于执行深度比较以及光照运算,然后也是获得mvp变换矩阵,法线变换矩阵,光源视点投影变换矩阵和各种光照参数的句柄.

有了这些句柄我们就可以给着色器中的常量传值了.

接着设置光照参数:

float lightd[4] = {1.0f, 1.0f, -1.0f, 0.0f};
float ambientLight[4] = {0.7f, 0.7f, 0.7f, 1.0f};
float diffuseLight[4] = {1.0f, 1.0f, 1.0f, 1.0f};
float ambientLightMtl[4] = {0.7f, 0.7f, 0.7f, 1.0f};
float diffuseLightMtl[4] = {1.0f, 1.0f, 1.0f, 1.0f};

void initLight() {
	D3DXVECTOR4 amb(ambientLight);
	D3DXVECTOR4 diff(diffuseLight);
	D3DXVECTOR4 lightDir(lightd);
	D3DXVECTOR4 ambMtl(ambientLightMtl);
	D3DXVECTOR4 diffMtl(diffuseLightMtl);
	texPixShader->constTable->SetVector(d3d,ambHand,&amb);
	texPixShader->constTable->SetVector(d3d,diffHand,&diff);
	texPixShader->constTable->SetVector(d3d,lightDirHand,&lightDir);
	texPixShader->constTable->SetVector(d3d,ambMtlHand,&ambMtl);
	texPixShader->constTable->SetVector(d3d,diffMtlHand,&diffMtl);
}

这几个光照参数各种固定管线教程中已经出现n多遍了,看到这些名字就应该知道意思了吧.

进行阴影贴图渲染的时候传递一下世界变换矩阵给shader,就像这样:

	D3DXMATRIX modelMatrix,scaleMat,rotXMat,rotYMat,tranMat;
	D3DXMatrixIdentity(&modelMatrix);
	D3DXMatrixScaling(&scaleMat,model->scale,model->scale,model->scale);
	D3DXMatrixRotationX(&rotXMat,atr(model->yr));
	D3DXMatrixRotationY(&rotYMat,atr(model->xr));
	D3DXMatrixTranslation(&tranMat,model->x,model->y,model->z);
	modelMatrix=modelMatrix*scaleMat*rotXMat*rotYMat*tranMat;
	storeVertexShader->constTable->SetMatrix(d3d,modelMatrixStoreHand,&modelMatrix);

	model->renderSimple();

进行普通场景渲染的时候还需要多传递一个法线变换矩阵,务必记住法线世界变换矩阵不是物体的世界变换矩阵,就像这样:

	D3DXMATRIX modelMatrix,scaleMat,rotXMat,rotYMat,tranMat;
	D3DXMatrixIdentity(&modelMatrix);
	D3DXMatrixScaling(&scaleMat,model->scale,model->scale,model->scale);
	D3DXMatrixRotationX(&rotXMat,atr(model->yr));
	D3DXMatrixRotationY(&rotYMat,atr(model->xr));
	D3DXMatrixTranslation(&tranMat,model->x,model->y,model->z);
	modelMatrix=modelMatrix*scaleMat*rotXMat*rotYMat*tranMat;
	texVertexShader->constTable->SetMatrix(d3d,modelMatrixHand,&modelMatrix);

	D3DXMATRIX normalMatrix;
	D3DXMatrixIdentity(&normalMatrix);
	D3DXMatrixInverse(&normalMatrix,NULL,&modelMatrix);
	D3DXMatrixTranspose(&normalMatrix,&normalMatrix);
	texVertexShader->constTable->SetMatrix(d3d,normalMatrixHand,&normalMatrix);

	model->render();

接着取得默认的渲染目标与深度缓存:

	d3d->GetRenderTarget(0,&defaultRenderTarget);
	d3d->GetDepthStencilSurface(&defaultDepthSurface);

然后创建阴影纹理,阴影纹理渲染目标与渲染阴影纹理使用的深度缓存:

	d3d->CreateTexture(shadowMapSize,shadowMapSize,1,D3DUSAGE_RENDERTARGET,
			D3DFMT_R32F,D3DPOOL_DEFAULT,&shadowMapTexture,NULL);
	d3d->CreateDepthStencilSurface(shadowMapSize,shadowMapSize,D3DFMT_D24X8,
			D3DMULTISAMPLE_NONE,0,TRUE,&shadowDepthSurface,NULL);
	shadowMapTexture->GetSurfaceLevel(0,&shadowRenderTarget);

请注意,这里阴影纹理的格式是R32F,这意味着阴影贴图只有r通道可用,gba通道不可用.

然后我们要的光源是方向光,那么光投影矩阵是个正交矩阵,要这么创建:

	float size=128;
	D3DXMatrixIdentity(&lightProjectionMatrix);
	D3DXMatrixOrthoOffCenterLH(&lightProjectionMatrix,-size,size,-size*2,size*2,-size*2,size*2);

这边的size可以自己定义,数值越大,阴影贴图范围越大,但是精细度下降.

随后建立光照空间视图矩阵:

	D3DXVECTOR3 viewPos(lightPos.x,lightPos.y,lightPos.z);
	D3DXVECTOR3 toVec(cx,cy,cz);
	D3DXVECTOR3 upVec(0.0f,1.0f,0.0f);
	D3DXMatrixIdentity(&lightViewMatrix);
	D3DXMatrixLookAtLH(&lightViewMatrix,&viewPos,&toVec,&upVec);

最后开始渲染部分,首先渲染阴影贴图:

void generateShadowMap(CallbackPtr callback) {
	d3d->SetRenderTarget(0,shadowRenderTarget);
	d3d->SetDepthStencilSurface(shadowDepthSurface);

	d3d->Clear(0,NULL,D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,D3DXCOLOR(1.0f,1.0f,1.0f,1.0f),1.0f,0);
	d3d->BeginScene();
	d3d->SetRenderState(D3DRS_CULLMODE,D3DCULL_CW);

	d3d->SetVertexShader(storeVertexShader->vsProg);
	d3d->SetPixelShader(storePixShader->psProg);

	storeVertexShader->constTable->SetMatrix(d3d,projMatrixStoreHand,&lightProjectionMatrix);
	storeVertexShader->constTable->SetMatrix(d3d,viewMatrixStoreHand,&lightViewMatrix);

	callback();

	d3d->SetRenderState(D3DRS_CULLMODE,D3DCULL_CCW);
	d3d->EndScene();
}

将渲染目标与深度缓存变为阴影贴图的,然后清除颜色和深度,都重设为1.0以表示最大深度,渲染深度可以采用顺时针剔除正面,然后就能够少渲染很多东西而且结果也不会受影响,渲染完毕之后再重设回逆时针.

然后计算光空间投影变换矩阵:

void generateShadowMatrix() {
	shadowMatrix=lightViewMatrix*lightProjectionMatrix;
}

随后进行一次普通场景渲染,把光空间投影变换矩阵传递给texVertexShader,以及摄像机使用的视图与投影变换矩阵:

void drawShadowMap(CallbackPtr callback) {
	d3d->SetDepthStencilSurface(defaultDepthSurface);
	d3d->SetRenderTarget(0,defaultRenderTarget);

	d3d->Clear(0,NULL,D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,D3DXCOLOR(0.5f,0.7f,0.8f,0.5f),1.0f,0);
	d3d->BeginScene();

	d3d->SetVertexShader(texVertexShader->vsProg);
	d3d->SetPixelShader(texPixShader->psProg);

	texVertexShader->constTable->SetMatrix(d3d,viewMatrixHand,&cameraViewMatrix);
	texVertexShader->constTable->SetMatrix(d3d,projMatrixHand,&cameraProjectionMatrix);
	texVertexShader->constTable->SetMatrix(d3d,shadowMatrixHand,&shadowMatrix);

	d3d->SetTexture(0,shadowMapTexture);

	callback();

	d3d->EndScene();
}

注意,在渲染之前把渲染目标和深度缓存切换到默认的,这是渲染到屏幕而不是渲染到纹理.然后清除默认颜色与深度缓存.

cpp编码到此结束,接下来看shader.

首先我们用storeVertexShader把场景顶点变换到光照空间,然后使用storePixShader保存场景深度值到阴影纹理

storeVertexShader:

float4x4 modelMatrix,viewMatrix,projMatrix;

struct Input {
	float4 position : POSITION;
	float4 normal : NORMAL;
	float2 texCoord0 : TEXCOORD0;
};

struct Output {
	float4 position : POSITION;
	float4 clipVert : TEXCOORD1;
};

Output main(Input input) {
	float4 modelVert = input.position;

	float4x4 mvpMatrix = mul(modelMatrix, mul(viewMatrix, projMatrix));
	float4 clipVert = mul(modelVert, mvpMatrix);

	Output output = (Output)0;
	output.position = clipVert;
	output.clipVert = clipVert;
	return output;
}

注意,最终的计算结果是属于光照裁剪空间,范围-w - +w.

storePixShader:

struct Input {
	float4 clipVert : TEXCOORD1;
};

struct Output {
	float4 pixColor : COLOR;
};

Output main(Input input) {
	float4 clipVert = input.clipVert;
	float depth = clipVert.z / clipVert.w;

	Output output = (Output)0;
	output.pixColor = float4(depth, depth, depth, depth);
	return output;
}

对深度值做齐次除法使其在0-1的范围.注意,输出纹理的范围在0-1,属于光照纹理空间.

接着正常渲染场景的时候使用texVertexShader与texPixShader.

texVertexShader:

float4x4 modelMatrix,viewMatrix,projMatrix,normalMatrix;
float4x4 shadowMatrix;

struct Input {
	float4 position : POSITION;
	float4 normal : NORMAL;
	float2 texCoord0 : TEXCOORD0;
};

struct Output {
	float4 position : POSITION;
	float2 texCoord0 : TEXCOORD0;
	float3 normal : TEXCOORD1;
	float4 shadowVert : TEXCOORD2;
};

Output main(Input input) {
	float4x4 modelViewProjMatrix = mul(modelMatrix, mul(viewMatrix, projMatrix));
	float4 modelVert = input.position;
	float4 modelNormal = input.normal;
	float2 texCoord0 = input.texCoord0;

	float3 worldNormal = mul(modelNormal.xyz, (float3x3)normalMatrix);
	float4 clipVert = mul(modelVert, modelViewProjMatrix);
	float4 worldVert = mul(modelVert, modelMatrix);
	float4 shadowVert = mul(worldVert, shadowMatrix);

	Output output = (Output)0;
	output.position = clipVert;
	output.texCoord0 = texCoord0;
	output.normal = worldNormal;
	output.shadowVert = shadowVert;
	return output;
}

shadowVert的计算结果是在光照裁剪空间,要在片段着色器中进行如下变换:

光照裁剪空间->光照标准坐标空间->光照纹理空间

切记,只有在同一空间内的向量才能进行运算.

接着是texPixShader:

float4 amb,diff,ambMtl,diffMtl,lightDir;

texture TextureShadow;
texture Texture1;
texture Texture2;

sampler texShadow = sampler_state {
	Texture = <Texture0>;
	MinFilter = LINEAR;
	MagFilter = LINEAR;
};
sampler tex1 = sampler_state {
	Texture = <Texture1>;
	MinFilter = LINEAR;
	MagFilter = LINEAR;
};
sampler tex2 = sampler_state {
	Texture = <Texture2>;
	MinFilter = LINEAR;
	MagFilter = LINEAR;
};

struct Input {
	float2 texCoord0 : TEXCOORD0;
	float3 normal : TEXCOORD1;
	float4 shadowVert : TEXCOORD2;
};

struct Output {
	float4 pixColor : COLOR;
};

float calcShadowFactor(float4 shadowVert) {
	float4 shadowNDCVert = shadowVert / shadowVert.w;
	float2 shadowVertCoord = 0.5 * shadowNDCVert.xy + float2(0.5, 0.5);
	shadowVertCoord.y = 1.0 - shadowVertCoord.y;
	float depthNow = shadowNDCVert.z;
	float depthStore = tex2D(texShadow, shadowVertCoord).r;
	float factor = 1.0;
	float bias = -0.00005;
	if(abs(shadowNDCVert.x) <= 1.0 &&
		abs(shadowNDCVert.y) <=1.0 &&
		abs(shadowNDCVert.z) <=1.0)
		factor = (depthNow + bias > depthStore) ? 0.0 : 1.0;
	return factor;
}

float4 calcLight(float3 normal,float shadowFactor) {
	float4 light = float4(0.0,0.0,0.0,1.0);
	float nDotL = max(dot(normalize(lightDir.xyz), normalize(normal)), 0.0);
	light = amb * ambMtl + diff * diffMtl * nDotL * shadowFactor;
	return light;
}

Output main(Input input) {
	float4 texColor = (0.6 * tex2D(tex1, input.texCoord0)) + (0.4 * tex2D(tex2, input.texCoord0));
	float shadowFactor = calcShadowFactor(input.shadowVert);
	float4 lightColor = calcLight(input.normal, shadowFactor);

	Output output = (Output)0;
	output.pixColor = lightColor * texColor;
	return output;
}

首先进行一次齐次除法计算shadowNDCVert 光照标准坐标空间坐标,接着对它进行坐标缩放与偏移取得shadowVertCoord 光照纹理空间坐标,注意OpenGL与DirectX对于纹理坐标表示的一点区别,OpenGL纹理坐标y轴向上,DirectX纹理坐标y轴向下,最后与阴影贴图中的深度相比取得阴影参数传递个光照计算函数计算光照,把阴影参数乘以漫反射光就可以得到阴影了,光照公式与之前基于顶点的光照公式一样.

下面看看结果如何:

有好多锯齿啊,没关系我们可以在生成阴影纹理的shader里采用一些过滤算法消除掉这些难看的锯齿.

具体的阴影过滤算法可以参考:

http://fabiensanglard.net/shadowmappingPCF/index.php

http://fabiensanglard.net/shadowmappingVSM/index.php

时间: 2024-11-10 01:24:40

DirectX光影详解的相关文章

各种音视频编解码学习详解

各种音视频编解码学习详解 媒体业务是网络的主要业务之间.尤其移动互联网业务的兴起,在运营商和应用开发商中,媒体业务份量极重,其中媒体的编解码服务涉及需求分析.应用开发.释放license收费等等.最近因为项目的关系,需要理清媒体的codec,比较搞的是,在豆丁网上看运营商的规范 标准,同一运营商同样的业务在不同文档中不同的要求,而且有些要求就我看来应当是历史的延续,也就是现在已经很少采用了.所以豆丁上看不出所以然,从 wiki上查.中文的wiki信息量有限,很短,而wiki的英文内容内多,删减版

PortAudio详解(2015-12-1更新)

PortAudio详解 整理者:赤子玄心 QQ:280604597 Email:[email protected] 大家有什么不明白的地方,或者想要详细了解的地方可以联系我,我会认真回复的 1   简介 PortAudio是一个免费.跨平台.开源的音频I/O库.看到I/O可能就想到了文件,但是PortAudio操作的I/O不是文件,而是音频设备.它能够简化C/C++的音频程序的设计实现,能够运行在Windows.Macintosh OS X和UNIX之上(Linux的各种版本也不在话下).使用P

run命令详解

1.gpedit.msc-----组策略 2.utilman--------辅助工具管理器 3. Nslookup-------IP地址侦测器 4. explorer-------打开资源管理器 5. logoff---------注销命令 6. tsshutdn-------60秒倒计时关机命令 7. lusrmgr.msc----本机用户和组 8. services.msc--- 本地服务设置 9. oobe/msoobe /a----检查XP是否激活 10. notepad--------

VLC命令行参数详解

VLC命令行参数详解 2012-11-29 14:00 6859人阅读 评论(0) 收藏 举报 Usage: vlc [options] [stream] ...You can specify multiple streams on the commandline. They will be enqueued in the playlist.The first item specified will be played first. Options-styles:  --option  A gl

nstallShield制作打包程序详解(图)

InstallShield产品,是安装工具领域事实上的标准.InstallShield 软件是软件安装.配置软件包和升级解决方案领域内公认的标准.InstallShield已经成为安全安装软件的标准解决方案,涉及全球6.9万多个开发组织和5亿台电脑.公司提供广泛的产品和服务,为软件供应商.系统管理员以及最终用户提供成功的销售.管理和应用安装.本文将以InstallShield10.5 Premier Edition为例详述打包的过程.使用工程助手(Project assistant)设计    

顶点着色器详解 (Vertex Shaders)

学习了顶点处理,你就知道固定功能流水线怎么将顶点从模型空间坐标系统转化到屏幕空间坐标系统.虽然固定功能流水线也可以通过设置渲染状态和参数来改变最终输出的结果,但是它的整体功能还是受限.当我们想实现一个外来的光照模型,外来的Fog或者点大小计算方式,等等,我们可能就放弃使用固定功能流水线,转而使用CPU来实现这些计算. 使用vertex shaders,它用一段小程序替换固定功能处理.这段小程序的输入是模型空间的顶点,输出齐次剪裁空间的顶点,并且还携带一些信息,如:per-vertex diffu

JavaFX学习之道:详解JavaFX架构与框架

JavaFX 2.0平台是基于Java技术的富客户端平台.它使应用程序开发者更加容易的开发和部署跨平台的富互联网应用(RIA).JavaFX 2.0文档包含了JavaFX 2.0所提供的功能的概述. 图1描述了JavaFX 2.0平台的架构组件.后面的部分将对每一个组件进行逐一的描述.在JavaFX通用API的下面是用来运行JavaFX代码的引擎.这个引擎包括以下子组件:JavaFX高性能图形引擎(Prism);新的更小但更有效率的窗体系统(Glass);媒体引擎和Web引擎.虽然这些组件不是包

windows进程详解

1:系统必要进程system process    进程文件: [system process] or [system process]进程名称: Windows内存处理系统进程描述: Windows页面内存管理进程,拥有0级优先.alg.exe       进程文件:alg or alg.exe 进程名称:应用层网关服务  描述:这是一个应用层网关服务用于网络共享csrss.exe      进程文件:csrss or csrss.exe 进程名称:Client/Server Runtime

常见图片格式详解

标明原作者信息 http://www.cnblogs.com/xiangism 做了几年有关图形.图像的工作,对图片格式算是小有经验,在此写成一文章总结下.虽然一开始并不想讲很理论的东西,但写完后发现几乎全是理论,细想一下关于图片格式的知识本身就是理论的东西,囧~~ 那就力求用最简单的方式将这些"理论"讲清楚吧. 常见的图片格式有bmp, jpg(jpeg), png, gif, webp等. 图像基本数据结构 要讲图片格式还先得从图像的基本数据结构说起.在计算机中, 图像是由一个个像