轻松愉快学Shader(1)——光照模型
来源:互联网 发布:王劲松演过的网络剧 编辑:程序博客网 时间:2024/06/05 20:24
第一讲我们先用Shader实现一个最基础的光照模型。固定管线中的3D光照在DX9的龙书中已经介绍的很详细了,但是一旦换成可编程流水线,许多光照算法的实现都要用Shader自己来写,这部分的公式龙书并未详细给出,因此有必要在这里小讲一下。如果这些内容你之前已经掌握了,那本章就当作一个回顾吧,因为接下来的几章内容都会以此光照模型为基础。
光照的组成:
我们都知道,D3D中把光分为环境光、漫反射光、镜面光。计算一个顶点的颜色,就是将这三种类型的光加起来。环境光是最简单的,只需要简单的加上去就可以了。漫反射光稍微复杂一些,它与光线的入射方向,以及顶点法线都有关系,计算公式如下:
衰减系数:k = saturate(cosθ) = saturate(dot(DirToLight, normal));
则顶点处的漫反射光颜色:DiffuseColor = LightColor * k;
其中DirToLight是由顶点位置指向光源位置的向量,注意需要把它化成单位向量。normal是该顶点的法线向量,同样也是一个单位向量,角θ就是这两个单位向量之间的夹角。很显然,这个角越小,顶点能吸收的光的强度越强。这里,将光线的颜色,乘以cosθ,就得到了该顶点能接收的光的强度,而cosθ呢
镜面光相比前两种光,其计算更加复杂。它不仅与光线方向和法线方向有光,而且与视点所在位置也有关系。如果能反射入我们眼睛的光线越多,当然我们看起来物体就越亮。镜面光的计算公式如下:
图中的所有向量都已经化成了单位向量,其中点A就是顶点所在的位置。向量AS就是顶点位置指向光源位置的向量,即DirToLight,向量AE是从顶点位置指向视点位置的单位向量,AN就是顶点的法线,AB这条线比较特殊,它相当于∠EAS的角平分线,同样也是一个单位向量,它的计算方式并不复杂:
AB = normalize(AE + AS)
即将AE和AS两个单位向量相加,根据向量加法的平行四边形法则,则得到的就是∠EAS角平分线,然后调用normalize函数将其规范化。我们真正关系的是角平分线与顶点法线的夹角θ,即∠NAB,这个角越是小,就说明视点位置与反射光线的偏差越小,也就是越多的光线会射入眼睛。读者可以想象一下是不是这么一回事,当角θ等于0时,所有的反射光线都会射进眼睛。此时光的反射方向与AE方向重合了,此时A点的镜面光强度最大。
总结起来,A点的镜面反射光强度计算公式如下:
衰减系数:
k = saturate(cosθ) = saturate(dot(AN, AB)) = saturate(dot(AN, normalize(AE + AS)))
则顶点处的镜面光颜色 :SpecularColor = LightColor * pow(k, Sharpness);
其中,为了让镜面光更加锐利,通常需要取cosθ的Sharpness次方。Sharpness的值越大,镜面光越锐利。
光源类型:
3D世界中的光源有三种:平行光、点光源、聚光灯。
具体三种光源的性质,请各位参看龙书上的讲解。我们这里只讲如何在Shader中实现它们。本节中我们选取点光源作为我们的光源类型。因为平行光过于简单了,聚光灯又过于复杂了。出于学习的目的,我们这里先重点讲解点光源。点光源的特点是光线从光源处向四面八方扩散,而且随着光线传播距离增大,光的强度会逐渐减弱。因此要计算空间中某个位置接收的点光源的光线强度,通常要乘以一个衰减系数。它的计算公式如下:
其中D是顶点到光源的距离,A0, A1, A2是表示光的常量、1次、2次衰减系数,这些值都是可以根据不同的情况进行指定的。
Shader算法实现:
说了这么多,大家都该烦了,下面直接上HLSL代码。首先需要在Shader文件里定义如下变量:
matrix MatWVP; // 世界、观察、投影变换矩阵的乘积float4 AmbientLight = {0.0f, 0.0f, 0.1f, 1.0f}; //环境光float3 LightPos = {3.0f, 3.0f, -3.0f}; //点光源位置float4 Attenuation = {1.0f, 0.1f, 0.0f, 0.0f}; //点光源的衰减系数,分别存储在x,y,z分量里float4 DiffuseLight = {0.0f, 0.0f, 1.0f, 1.0f}; //漫反射光float4 SpecularLight = {1.0f, 1.0f, 1.0f, 1.0f}; //镜面光float4 ViewPos; //视点位置struct VS_OUTPUT{float4 Position : POSITION0;float4 Color : COLOR0;};这些变量和声明都不难理解,其中MatWVP和ViewPos两个变量是需要在DX9程序中传入的,因此在Shader文件中就没有初始化。VS_OUTPUT是顶点着色器需要返回的类型。
下面我们自定义了一个函数,也是最核心的功能——计算某个顶点的颜色:
//计算某个顶点的颜色float4 CalculateLightColor(float4 Pos, float3 normal){//从顶点到光源的向量float3 LightDir = LightPos - Pos;//顶点与光源之间的距离float Dist = length(LightDir);//将LightDir向量化成单位向量LightDir = LightDir / Dist;//计算点光源的衰减系数,距离越大,光就越弱float DistAtten = 1.0f / (Attenuation.x + Attenuation.y * Dist + Attenuation.z * Dist * Dist);//计算漫反射光的衰减系数float AngleAtten = saturate(dot(LightDir, normal));//顶点到视点的向量float3 EyeDir = normalize(ViewPos.xyz - Pos.xyz);//上图中的角平分线,即向量ABfloat3 HalfVect = normalize(LightDir + EyeDir);//计算镜面光的衰减系数float SpecAtten = saturate(dot(normal, HalfVect));//最终顶点处的颜色,这里指定Sharpness的值是32return DistAtten * (DiffuseLight * AngleAtten + SpecularLight * pow(SpecAtten, 32));}
很显然,这个CalculateLightColor函数根据传入顶点位置和法线向量,计算该顶点的光照颜色。有的读者会注意到这里似乎忘了加上环境光(AmbientLight),其实没关系,由于环境光通常无需差值处理,因此我们在像素着色器里再加上。
下面就是该Shader的顶点着色器和像素着色器的代码:
VS_OUTPUT vs_main(float4 Pos : POSITION0, float3 normal : NORMAL){VS_OUTPUT output = (VS_OUTPUT)0;output.Position = mul(Pos, MatWVP);output.Color = CalculateLightColor(Pos, normal);return output;}float4 ps_main(VS_OUTPUT input) : COLOR0{return input.Color + AmbientLight;}
是不是非常简单呢?在顶点着色器里,我们只需要根据MatWVP矩阵变换顶点位置,同时计算该顶点处的光照颜色。在像素着色器里,读取到的某像素颜色是根据邻近顶点进行差值处理后的值,该值仅仅包含漫反射光和镜面光,因此需要加上环境光颜色。
源代码欣赏:
在本节的demo中,我们在主程序中画了一个球体,并用点光源对它进行照射。下面时C++部分的代码:
#include <d3d9.h>#include <d3dx9.h>#pragma comment(lib,"d3d9.lib") #pragma comment(lib,"d3dx9.lib") #pragma comment(lib,"dxguid.lib") #pragma comment(lib, "winmm.lib")#define SCREEN_WIDTH 800#define SCREEN_HEIGHT 600#define SAFE_RELEASE(p) {if(p){(p)->Release(); (p)=NULL;}}LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);bool Game_Init(HWND hwnd);void Game_Update(float);void Game_Render();void Game_Exit();LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;LPD3DXMESH g_pSphere = NULL; //球体LPD3DXEFFECT g_pEffect = NULL; //效果框架int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int iCmdShow){ HWND hwnd; MSG msg; WNDCLASS wndclass; static TCHAR szClassName[ ] = TEXT("WindowsApp"); /* 第一步:注册窗口类 */ wndclass.hInstance = hInstance; wndclass.lpszClassName = szClassName; wndclass.lpfnWndProc = WindowProcedure; wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; wndclass.cbWndExtra = 0;wndclass.cbClsExtra = 0;wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);wndclass.lpszMenuName = NULL; if (!RegisterClass (&wndclass)) return 0;RECT rc = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT};AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, false); /* 第二步:创建窗口 */hwnd = CreateWindow(szClassName, TEXT("MyApp"), WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top,NULL,NULL,hInstance,NULL); /* 第三步:显示窗口 */ ShowWindow (hwnd, iCmdShow);UpdateWindow(hwnd);Game_Init(hwnd);float lastTime = timeGetTime() * 0.001f;float currentTime, delta; /* 第四步:消息循环 */while(true){if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){if(msg.message == WM_QUIT)break;TranslateMessage(&msg);DispatchMessage(&msg);}else {currentTime = timeGetTime() * 0.001f;delta = currentTime - lastTime;lastTime = currentTime;Game_Update(delta);Game_Render();}}Game_Exit();return msg.wParam;}/*窗口过程*/LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){ switch (message) {case WM_DESTROY:PostQuitMessage (0); break;default:return DefWindowProc (hwnd, message, wParam, lParam); } return 0;}bool Game_Init(HWND hwnd){/* D3D初始化 */LPDIRECT3D9 d3d9 = Direct3DCreate9(D3D_SDK_VERSION);D3DCAPS9 d3dcap;d3d9->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &d3dcap);DWORD vp;if( d3dcap.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;elsevp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&d3dpp, sizeof(d3dpp));d3dpp.BackBufferWidth = SCREEN_WIDTH;d3dpp.BackBufferHeight = SCREEN_HEIGHT;d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;d3dpp.BackBufferCount = 1;d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;d3dpp.MultiSampleQuality = 0;d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.hDeviceWindow = hwnd;d3dpp.Windowed = true;d3dpp.EnableAutoDepthStencil = true; d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;d3dpp.Flags = 0;d3dpp.FullScreen_RefreshRateInHz = 0;d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;HRESULT hr;hr = d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, vp, &d3dpp, &g_pd3dDevice);d3d9->Release();if(FAILED(hr))return false;//创建球体网格, 球体半径为1.0D3DXCreateSphere(g_pd3dDevice, 1.0f, 20, 20, &g_pSphere, NULL);//初始化效果框架LPD3DXBUFFER pCompilerError;hr = D3DXCreateEffectFromFileA(g_pd3dDevice, "effect.fx", NULL, NULL,D3DXSHADER_DEBUG, NULL, &g_pEffect, &pCompilerError);if(FAILED(hr)){if(pCompilerError){MessageBoxA(NULL, (char*)pCompilerError->GetBufferPointer(), NULL, 0);pCompilerError->Release();}return false;}D3DXHANDLE hTech = g_pEffect->GetTechniqueByName("T");g_pEffect->SetTechnique(hTech);return true;}void Game_Update(float delta){/* 计算观察矩阵和投影矩阵,并传入Shader */D3DXMATRIX matView;D3DXVECTOR3 vEye(0.0f, 0.0f, -5.0f);D3DXVECTOR3 vAt(0.0f, 0.0f, 0.0f); D3DXVECTOR3 vUp(0.0f, 1.0f, 0.0f);D3DXMatrixLookAtLH(&matView, &vEye, &vAt, &vUp);D3DXMATRIX matProj; D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI / 4.0f, (float)SCREEN_WIDTH / (float)SCREEN_HEIGHT, 1.0f, 1000.0f); D3DXHANDLE hMatWVP = g_pEffect->GetParameterByName(NULL, "MatWVP");g_pEffect->SetMatrix(hMatWVP, &(matView * matProj));//将视点位置传入ShaderD3DXHANDLE hViewPos = g_pEffect->GetParameterByName(NULL, "ViewPos");g_pEffect->SetVector(hViewPos, &D3DXVECTOR4(vEye.x, vEye.y, vEye.z, 1.0f));}void Game_Render(){g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0); g_pd3dDevice->BeginScene();g_pEffect->Begin(0, 0);g_pEffect->BeginPass(0);g_pSphere->DrawSubset(0);g_pEffect->EndPass();g_pEffect->End();g_pd3dDevice->EndScene();g_pd3dDevice->Present(NULL, NULL, NULL, NULL);}void Game_Exit(){SAFE_RELEASE(g_pSphere)SAFE_RELEASE(g_pEffect)SAFE_RELEASE(g_pd3dDevice);}
效果文件effect.fx中的代码如下,和刚才讲的没什么不同:
matrix MatWVP; // 世界、观察、投影变换矩阵的乘积float4 AmbientLight = {0.0f, 0.0f, 0.1f, 1.0f}; //环境光float3 LightPos = {3.0f, 3.0f, -3.0f}; //点光源位置float4 Attenuation = {0.5f, 0.1f, 0.0f, 0.0f}; //点光源的衰减系数,分别存储在x,y,z分量里float4 DiffuseLight = {0.0f, 0.0f, 1.0f, 1.0f}; //漫反射光float4 SpecularLight = {1.0f, 1.0f, 1.0f, 1.0f}; //镜面光float4 ViewPos; //视点位置struct VS_OUTPUT{float4 Position : POSITION0;float4 Color : COLOR0;};//计算某个顶点的颜色float4 CalculateLightColor(float4 Pos, float3 normal){//从顶点到光源的向量float3 LightDir = LightPos - Pos;//顶点与光源之间的距离float Dist = length(LightDir);//将LightDir向量化成单位向量LightDir = LightDir / Dist;//计算点光源的衰减系数,距离越大,光就越弱float DistAtten = 1.0f / (Attenuation.x + Attenuation.y * Dist + Attenuation.z * Dist * Dist);//计算漫反射光的衰减系数float AngleAtten = saturate(dot(LightDir, normal));//顶点到视点的向量float3 EyeDir = normalize(ViewPos.xyz - Pos.xyz);//上图中的角平分线,即向量ABfloat3 HalfVect = normalize(LightDir + EyeDir);//计算镜面光的衰减系数float SpecAtten = saturate(dot(normal, HalfVect));//最终顶点处的颜色,这里指定Sharpness的值是32return DistAtten * (DiffuseLight * AngleAtten + SpecularLight * pow(SpecAtten, 32));}VS_OUTPUT vs_main(float4 Pos : POSITION0, float3 normal : NORMAL){VS_OUTPUT output = (VS_OUTPUT)0;output.Position = mul(Pos, MatWVP);output.Color = CalculateLightColor(Pos, normal);return output;}float4 ps_main(VS_OUTPUT input) : COLOR0{return input.Color + AmbientLight;}technique T{pass P0{Lighting = false;VertexShader = compile vs_2_0 vs_main();PixelShader = compile ps_2_0 ps_main();}}
程序的运行截图:
本节源代码下载请点击这里
- 轻松愉快学Shader(1)——光照模型
- 轻松愉快学Shader(2)——Phong着色
- Shader山下(二)光照模型——漫反射
- Unity3d Shader光照模型
- <Shader> 自定义光照模型
- unity shader学习笔记(一)——基础光照之标准光照模型定义
- 一步一步学RenderMonkey(2)——光照模型
- 一步一步学RenderMonkey(3)——改良Phong光照模型
- 一步一步学RenderMonkey(2)——光照模型
- 一步一步学RenderMonkey(3)——改良Phong光照模型
- Unity Shader自定义光照模型
- shader 光照模型,半角向量
- Unity Shader 五 光照模型
- shader中一些光照模型
- 一步步学shader系列(1):环境光照(Ambient light)
- shader之——自定义shader射线光照烘焙
- 轻松愉快的实训课——C51程序设计
- 主流光照模型简介(1)——Lambert模型
- DataList的用法
- tomcat优化方案总结
- Exception---Android中处理崩溃异常
- 中国最牛的C/C++课程,谁欲与之试比高?
- 优先队列
- 轻松愉快学Shader(1)——光照模型
- Location---Location服务之LocationManager
- asp.net/c# 用<input type="file" />实现文件上传,multipart/form-data
- 11个高效的VS调试技巧
- hdu 1401 Solitaire (char数组判重节约内存+伪dbfs)
- svn chectout之后,造成桌面蓝色问号
- hdu 1114 Piggy-Bank
- hdu-A + B Problem II
- inet_pton函数说明