问题

使用你配置好的光照,BasicEffect可以很好地绘制场景。但是,如果你想定义一些更酷的效果,首先要实现的就是正确的光照。

本教程中,你将学习如何编写一个基本的HLSL effect实现逐顶点光照。

解决方案

传递每个顶点的3D位置和法线到effect中。显卡上的vertex shader需要对每个顶点做两件事。

首先,当绘制3D世界时,总是要使用世界矩阵,视矩阵和投影矩阵将3D位置转换为对应的2D屏幕坐标。

第二,通过叉乘光线方向和法线方向计算顶点的光照强度。

工作原理

首先需要在XNA项目中定义顶点。显然你需要将3D位置存储在每个顶点中。要在vertex shader 中计算正确的光照,你还需要为每个顶点提供法线,可参见教程6-1理解法线的概念。

你可以使用教程6-1中的相同代码,这个代码创建了包含一个3D位置和一个法线(还包含纹理坐标,只是这里你不使用它们)的六个顶点。

在XNA项目中创建一个新的. fx文件,添加以下代码。它包含了可以从XNA应用程序中改变的HLSL变量。

float4x4 xWorld;
float4x4 xView;
float4x4 xProjection;
float xAmbient; float3 xLightDirection;

当将一个3D坐标转换到2D屏幕坐标时,总是需要视矩阵和投影矩阵(见教程2-1)。因为你还想在场景中移动物体,所以还需一个世界矩阵(见教程4-2)。因为这个教材处理的是光照,你需要定义光线的方向。Ambient变量让你可以设置光照的最小级别,这样,即使一个对象没有被光源直接照射,它仍是隐约可见的。

在进入vertex shader和pixel shader前,首先需要定义output结构。首先,vertex shader的output 就是pixel shader的input,必须保存每个顶点的2D屏幕坐标。第二,vertex shader 还计算了每个顶点的光照强度。

在vertex shader和pixel shader之间,这些值进行了插值,让每个像素获取了它们各自的插值。

pixel shader仅计算每个像素的最终颜色。

struct VSVertexToPixel
{float4 Position : POSITION; float LightingFactor : TEXCOORD0;
};struct VSPixelToFrame
{float4 Color : COLOR0;
};
Vertex Shader

vertex shader将World,View和Projection矩阵组合成一个矩阵,用来将3D坐标转换为2D屏幕坐标。

给定光线方向和法线方向,vertex shader可以根据图6-7计算光照强度。光线和法线间的夹角越小,光照越强烈,夹角越大,光照越少。

你可以通过点乘这两个方向获得这个值。点乘返回一个0到1之间的值(如果两个向量的长度都是1)。

图6-7 光线方向和法线方向的点乘

但是,在计算两者的点乘时首先要将其中一个方向反向,否则这两个方向是相反的。例如,图6-7右图中你发现法线和光线方向是相反的,这会导致点乘的结果为负。

VSVertexToPixel VSVertexShader(float4 inPos: POSITION0, float3 inNormal: NORMAL0)
{VSVertexToPixel Output = (VSVertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection); float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); float3 normal = normalize(inNormal); Output.LightFactor = dot(rotNormal, -xLightDirection); return Output;
}

点乘的结果是一个single值,基于两个法线的夹角和长度。在大多数情况中,你只需要光线基于两者的夹角。这意味着你需要确保3D空间中的所有法线和光线方向长度是一样的;否则,具有更长法线的顶点会获取更多的光照。

这可以通过让所有法线的长度为1做到,即需要归一化法线。

注意:归一化(normalizing)的意思不是对法线不做操作,而是让一个向量的长度变为1,可参见教程6-1。

使用世界矩阵时确保正确的光照

前面的代码在世界矩阵为单位矩阵时工作良好,即物体需要放置在(0,0,0)3D空间的初始位置(见教程5-2)。

但在大多数情况中,你想使用另外的世界矩阵,让你可以移动/旋转/缩放对象。

如图6-1所示,如果你旋转了物体,法线也会跟着一起旋转。这意味着法线需要通过世界矩阵中的旋转量进行变换。

世界矩阵中的缩放操作不会影响光照的计算。你总要在vertex shader中归一化法线,让向量的长度变为1。

但是,如果世界矩阵中包含平移,你就会遇到麻烦。这是因为法线是最大长度为1的向量。例如,当你使用一个包含超过两个单位的矩阵变换法线时,所有的法线都会指向那个方向。

如图6-8所示,一个物体使用一个包含平移一段距离的世界矩阵向右平移时,顶点的位置会移向右方。法线也会根据这个世界矩阵移向右方,但它们的方向应该是不变的。所以,在使用世界矩阵变换法线时,你需要将世界矩阵中的平移部分剥离出来。

图6-8 被世界矩阵中的平移影响的法线

矩阵是一个包含4 × 4个数字的表格。你应该只使用世界矩阵中的旋转部分变换法线,而不要用平移部分。你可以提取出矩阵的旋转部分,它位于左上的3 × 3的数字中。只需简单地将4 × 4世界矩阵变换为一个3 × 3矩阵,就可以只获取旋转信息,这正是你所需要的!使用这个矩阵旋转法线,代码如下所示:

float3 normal = normalize(inNormal);
float3x3 rotMatrix = (float3x3)xWorld;
float3 rotNormal = mul(normal, rotMatrix);
Output.LightFactor = dot(rotNormal, -xLightDirection);
Pixel Shader

首先,三角形的三个顶点由vertex shader进行处理,计算光照值。然后,对三角形中的每个像素,这个光照值会在三个顶点间进行插值。这个插值过的光照值传递到pixel shader。

在这个简单地例子中,取蓝色为物体的基本颜色。要在三角形上添加明暗效果,要将这个基本颜色乘以LightFactor (在前面的vertex shader中计算)和环境光照(由XNA程序通过xAmbient变量设置)。环境光(ambient)因子确保所有物体不会是完全黑暗的,而LightFactor 根据光线方向施加对应的光照:

VSPixelToFrame VSPixelShader(VSVertexToPixel PSIn) : COLOR0
{VSPixelToFrame Output = (VSPixelToFrame)0; float4 baseColor = float4(0,0,1,1); Output.Color = baseColor*(PSIn.LightFactor+xAmbient); return Output;
}
定义technique

最后,定义technique:

technique VertexShading
{pass Pass0 {VertexShader = compile vs_2_0 VSVertexShader(); PixelShader = compile ps_2_0 VSPixelShader(); }
}
XNA代码

在XNA项目中,导入HLSL文件并将它存储在一个Effect变量中,这和教程3-1中对纹理的操作是类似的。在本例中,HLSL文件名为vertexshading. fx:

effect = content.Load<Effect>("vertexshading");

当绘制物体时,首先需要设置effect的参数,这需要用到BasicEffect:

effect.CurrentTechnique = effect.Techniques["VertexShading"];
effect.Parameters["xWorld"].SetValue(Matrix.Identity);
effect.Parameters["xView"].SetValue(fpsCam.ViewMatrix);
effect.Parameters["xProjection"].SetValue(fpsCam.ProjectionMatrix);
effect.Parameters["xLightDirection"].SetValue(new Vector3(1, 0, 0)); effect.Begin();
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{ pass.Begin(); device.VertexDeclaration = myVertexDeclaration; device.DrawUserPrimitives<VertexPositionNormalTexture>(PrimitiveType.TriangleList, vertices, 0, 2); pass.End();
}
effect.End();
代码

XNA代码绘制对象的多个实例。因为使用了不同的世界矩阵,这些对象会绘制在不同位置。

最终结果和教程6-1是一样的,只是这次你使用了自己的HLSL effect:

effect.CurrentTechnique = effect.Techniques["VertexShading"]; effect.Parameters["xView"].SetValue(fpsCam.ViewMatrix);
effect.Parameters["xProjection"].SetValue(fpsCam.ProjectionMatrix);
effect.Parameters["xLightDirection"].SetValue(new Vector3(1, 0, 0));
effect.Parameters["xAmbient"].SetValue(0.0f); for (int i = 0; i < 9; i++)
{Matrix world = Matrix.CreateTranslation(4, 0, 0) * Matrix.CreateRotationZ((float)i * MathHelper.PiOver2 / 8.0f); effect.Parameters["xWorld"].SetValue(world); effect.Begin(); foreach (EffectPass pass in effect.CurrentTechnique.Passes) {pass.Begin(); device.VertexDeclaration = myVertexDeclaration; device.DrawUserPrimitives<VertexPositionNormalTexture> (PrimitiveType.TriangleList, vertices, 0, 2); pass.End(); }effect.End();
}

下面是.fx文件的完整内容:

float4x4 xWorld;
float4x4 xView;
float4x4 xProjection;
float xAmbient;
float3 xLightDirection; struct VSVertexToPixel
{float4 Position : POSITION; float LightFactor : TEXCOORD0;
}; struct VSPixelToFrame
{float4 Color : COLOR0;
}// Technique: VertexShading
VSVertexToPixel VSVertexShader(float4 inPos: POSITION0, float3 inNormal: NORMAL0)
{VSVertexToPixel Output = (VSVertexToPixel)0; float4x4 preViewProjection = mul(xView, xProjection); float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); float3 normal = normalize(inNormal); float3x3 rotMatrix = (float3x3)xWorld; float3 rotNormal = mul(normal, rotMatrix); Output.LightFactor = dot(rotNormal, -xLightDirection); return Output;
}VSPixelToFrame VSPixelShader(VSVertexToPixel PSIn) : COLOR0
{ VSPixelToFrame Output = (VSPixelToFrame)0; float4 baseColor = float4(0,0,1,1); Output.Color = baseColor*(PSIn.LightFactor+xAmbient); return Output;
}technique VertexShading
{pass Pass0 {VertexShader = compile vs_2_0 VSVertexShader(); PixelShader = compile ps_2_0 VSPixelShader(); }
}

转载于:https://www.cnblogs.com/AlexCheng/archive/2011/02/14/2120098.html

在场景中添加光线——添加HLSL Vertex Shading相关推荐

  1. [unreal4入门系列之十] UE4添加角色到场景中

    现在我们已经有了一个场景并且运行了,我们需要添加一个角色到场景中.要这样做,我们必须从UE4的GameFramework类继承它. 一. 打开上次创建的关卡 如果你已经关闭了项目,并且保存了上次创建的 ...

  2. 在场景中添加光线——在Deferred Shading引擎中添加阴影能力

    问题 虽然你已经掌握了基本的计算机实时光照,但你应该注意到光源还没有投射出阴影.这是因为pixel shader是基于光线与法线的夹角计算光照的.直到现在,pixel shader还没有考虑到光线与像 ...

  3. js添加网页水印和three.js场景中加水印

    我们在日常网页开发的时候,可能想给自己的网页或者canvas里面添加水印,增添个人标记,我这里分为普通静态html页面和threejs中3d场景里面添加水印功能. 一 静态html页面添加水印 你只需 ...

  4. [unreal4入门系列之八] 使用VS编译UE4编辑器并添加物体到场景中

    接下来,我们将会创建一个放置我们的游戏角色的基本场景. 编译UE4编辑器 1) 现在,我们创建一个空白的UE4项目来开始.首先点击桌面的Epic Games Launcher,打开启动器,点击左上角的 ...

  5. NVisionXR_iOS教程八 —— 场景中添加粒子

    本章节将介绍如何往场景里添加粒子效果.打开上一章节的代码  1.添加头文件#import ''NVWorldParticles.h''  2.将粒子的资源文件拖入到UserRes.bundle目录下( ...

  6. 【Unity3D】使用 FBX 格式的外部模型 ( 向 Unity 中添加 FBX 模型 | 向 Scene 场景中添加 FBX 模型 | 3D 物体渲染 | 3D 物体材质设置 )

    文章目录 一.向 Unity 中添加 FBX 模型 二.向 Scene 场景中添加 FBX 模型 三.3D 物体渲染 四.3D 物体材质设置 一.向 Unity 中添加 FBX 模型 Unity 中使 ...

  7. UE4 学习记录八 给场景中添加背景音乐和动画音效,运动加速效果

    这只是用来记录我学习UE4过程的,可能帮不到你,先说声抱歉.为了防止误导他人,请勿转载,请勿转载,请勿转载. 本文的主题是给场景中添加背景音乐和动画的音效.总章目录(https://blog.csdn ...

  8. 【企业数字化转型】数据可视化技术:Three.js 用Physijs在场景中添加物理效果

    Three.js 极简教程 简介 Three.JS 是什么 Three.JS是基于WebGL的Javascript开源框架,简言之,就是能够实现3D效果的JS库. Three.JS 能做什么 利用Th ...

  9. three.js后期处理-使用UnrealBloomPass通道在场景中添加泛光效果,三维物体表面发光效果(vue中使用three.js85)

    使用UnrealBloomPass通道在场景中添加泛光效果 1.demo效果 2. 重要知识点 2.1 回顾要点 2.2 UnrealBloomPass通道介绍 3. 实现要点 3.1 相关文件引入 ...

最新文章

  1. 数据蒋堂 | “后半”有序的分组
  2. Unity TIP4: 带泛型参数的接口注入(interface,generic)
  3. boost::gregorian模块实现使用公历精确地推进一个月的测试程序
  4. arouter跨module传递消息_利用ARouter实现组件间通信,解决子模块调用主模块问题...
  5. CSS之七个高度有效的媒体查询技巧
  6. 计算机专业英语词汇pdf,计算机专业英语词汇1700词.pdf
  7. Excel怎么删除表格最后的空行
  8. python实现规则引擎_规则引擎python
  9. 德国IT行业薪酬水平大揭秘--2020
  10. ssm+boot+thymeleaf博客系统完成总结
  11. 卫星遥感技术应用与农业干旱监测的研究进展
  12. HTTP Status 500 - Servlet.init() for servlet DispatcherServlet threw exception
  13. 关于掉落的详细分析。
  14. 独立开发者:新手做2D手游该用哪些工具
  15. 卡布奇诺搭建教程_移动Web应用程序框架匹配,第2部分,探索卡布奇诺咖啡以进行移动Web应用程序开发
  16. sqljdbc4.jar和sqljdbc.jar下载
  17. “数字炸弹“——练习Python基础知识的小游戏【文末源码地址】
  18. OSChina 周日乱弹 —— 请务必让我分担他们的痛苦!
  19. 银河麒麟V10 - postgresql/postgis完整部署
  20. 特斯拉冻结招聘或因「幽灵刹车」故障,全球大厂掀裁员潮人心惶惶

热门文章

  1. php+js实现异步图片上传,JavaScript实现异步图像上传功能
  2. 数据库连接php_php 连接 数据库
  3. java 三级菜单栏的添加_[Java教程]jquery实现的三级导航菜单实例代码
  4. shell mysql awk_shell mysql 处理数据小结
  5. oracle没用过元数据,案例:Oracle RAC asm备份元数据之md_backup和md_restore 好处与
  6. 【补丁分析】CVE-2016-8610:对导致拒绝服务的“SSL Death Alert”漏洞补丁分析
  7. 信安教程第二版-第11章网络物理隔离技术原理与应用
  8. ipv4广播地址怎么填_什么是IP地址?IP地址有什么用?网络工程师来告诉你
  9. div 重新加载_JS之 加载模糊文本动画
  10. MongoDB模糊查询-查询某月的数据