请输入您要查询的百科知识:

 

词条 像素着色器
释义

在Microsoft DirectX 8.0之前,Microsoft Direct3D 使用固定功能流水线把三维几何体转换为屏幕上的像素。用户通过设置流水线的属性来控制Direct3D进行变换、光照和渲染像素的方式。固定功能顶点格式在编译的时候定义并决定输入顶点的格式,一旦定义,用户在运行的时候就几乎无法控制流水线的改变。

通过允许对顶点的变换、光照和对每个像素的着色等功能进行编程,着色器把图形流水线引入了一个新的高度。像素着色器是一些小程序,在对三角形进行光栅化操作时运行。这在渲染像素的方法上给了用户更高一级的灵活性。

像素着色器包含由ASCII文本组成的像素着色器指令。算术指令可以用来进行漫反射和/或镜面反射光照计算。纹理寻址指令提供了多种读取和应用纹理数据的操作。着色器具有这样的功能,可以给颜色分量设置掩码以及交换颜色分量。着色器的正文看起来有点像汇编语言,它用Direct3D扩展(D3DX)进行汇编,输入可以是文本字符串或是文件。汇编器的输出是一系列操作码,应用程序可以通过IDirect3DDevice9::CreatePixelShader方法把这些操作码提供给Direct3D。

本示例用像素着色器对一个四边形的漫反射色进行高洛德插值。示例显示了着色器文件的内容以及应用程序中所需的代码。

以下是创建像素着色器所需的步骤:

第1步:检查对像素着色器的支持。 第2步:声明顶点数据。 第3步:设计像素着色器。 第4步:创建像素着色器。 第5步:渲染输出像素。 如果读者已经知道如何构建并运行Direct3D示例,那么可以从本示例中复制代码并粘贴到已有的应用程序中。

第1步

要检查对像素着色器的支持,应该使用以下代码。这个例子检查1.1版本的像素着色器。

D3DCAPS9 caps;

m_pd3dDevice->GetDeviceCaps(&caps); // 使用m_pd3dDevice前要进行初始化

if( caps.PixelShaderVersion < D3DPS_VERSION(1,1) )

return E_FAIL;

caps结构会返回硬件可用的能力。要用D3DPS_VERSION宏检查当前硬件支持的所有着色器版本。如果caps返回的版本小于1.1,那么这个调用会失败。反之,对所有大于或等于1.1的版本,调用会成功。如果硬件不支持被测试的着色器版本,那么应用程序将不得不退而使用别的渲染方法(也许可以使用一个较低版本的着色器)。

第2步

这个示例使用了一个四边形,由两个三角形组成。每个顶点的数据结构包含了位置和漫反射色数据。D3DFVF_CUSTOMVERTEX宏定义了与顶点数据相匹配的数据结构。实际的顶点数据在全局数组g_Vertices中声明。四个顶点以原点为中心,每个顶点具有不同的漫反射色。

// 声明顶点数据结构。

struct CUSTOMVERTEX

{

FLOAT x, y, z;

DWORD diffuseColor;

};

// 声明自定义FVF宏。

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)

// 声明顶点位置和漫反射色数据。

CUSTOMVERTEX g_Vertices[]=

{

// x y z 漫反射色

{ -1.0f, -1.0f, 0.0f, 0xffff0000 }, // 红 – 左下

{ +1.0f, -1.0f, 0.0f, 0xff00ff00 }, // 绿 – 右下

{ +1.0f, +1.0f, 0.0f, 0xff0000ff }, // 蓝 – 右上

{ -1.0f, +1.0f, 0.0f, 0xffffffff }, // 白 – 左上

};

第3步

这个着色器把经过高洛德插值的漫反射色数据复制到输出像素。着色器文件PixelShader.txt如下所示:

ps_1_1 // 版本指令

mov r0,v0 // 把顶点的漫反射色复制到输出寄存器。

像素着色器文件的第一条指令声明了像素着色器的版本,此处为1.1。

第二条指令把颜色寄存器(v0)的内容复制到输出寄存器(r0)。因为在第1步中声明的顶点数据已经包含了经过插值的漫反射色,所以颜色寄存器包含了顶点的漫反射色。输出寄存器决定渲染目标使用的像素颜色(因为本例中没有更多的处理,如雾,所以输出寄存器就是最终的像素颜色)。

第4步

像素着色器由像素着色器指令创建。本例中,指令被包含在一个单独的文件中。指令也可以被包含在一个文本字符串中。

LPD3DXBUFFER pCode; // 存放经过汇编的着色器代码的缓存

LPD3DXBUFFER pErrorMsgs; // 存放错误信息的缓存

TCHAR strPixelShaderPath[512];// 用来定位着色器文件

DXUtil_FindMediaFileCb( strPixelShaderPath, sizeof(strPixelShaderPath),

_T("PixelShader.txt") );

这个函数是示例框架使用的一个辅助函数,许多示例都以它为基础。

LPDIRECT3DPIXELSHADER9 m_pPixelShader;

D3DXAssembleShaderFromFile( strPixelShaderPath, NULL, NULL, 0,

&pCode, &pErrorMsgs, NULL );

m_pd3dDevice->CreatePixelShader( (DWORD*)pCode->GetBufferPointer(),

&m_pPixelShader );

着色器创建完成后,指针m_pPixelShader用来对它进行引用。

第5步

除了用像素着色器句柄来设置着色器外,渲染输出像素的过程和使用固定功能流水线类似。

// 在本例中关闭光照。它不会对最终像素的颜色产生影响。

// 像素颜色完全由经过插值的顶点颜色决定。

m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE );

m_pd3dDevice->SetStreamSource( 0, m_pQuadVB, sizeof(CUSTOMVERTEX) );

m_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );

m_pd3dDevice->SetPixelShader( m_pPixelShader );

m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );

源顶点数据由SetStreamSource设置。本例中,SetFVF使用在声明顶点数据时定义的FVF码告诉Direct3D进行固定功能顶点处理。顶点着色器和像素着色器既可以一起使用,也可以分开使用。可以用固定功能流水线代替这两者。SetPixelShader设置像素着色器,而DrawPrimitive则绘制四边形。

确认对像素着色器的支持

应用程序可以查询D3DCAPS9的成员以确定硬件对像素着色器所涉及的操作的支持程度。下表列出了与可编程像素处理有关的设备能力。

设备能力 描述

PixelShader1xMaxValue 寄存器中可存储的值的范围为[-PixelShader1xMaxValue, PixelShader1xMaxValue]。这个值只对版本1.1到1.4有效。

MaxSimultaneousTextures 用于固定功能流水线,纹理取样器的数量为MaxTextureBlendStages除以MaxSimultaneousTextures。用于像素着色器的纹理取样器的数量在接下来的表中显示。

PixelShaderVersion 硬件支持的像素着色器的版本。版本号小于或等于该值的像素着色器被支持。

可用于像素着色器的纹理取样器的数量取决于像素着色器的版本。

像素着色器版本 纹理取样器的数量

ps_1_1 - ps_1_3 4个纹理取样器

ps_1_4 6个纹理取样器

ps_2_0 - ps_3_0 16个纹理取样器

Fixed function pixel shader MaxTextureBlendStages/MaxSimultaneousTextures个纹理取样器PixelShaderVersion的第一个字节包含次版本号,第二个字节包含主版本号。经过汇编的着色器的第一个标记就是像素着色器的版本。每种硬件实现都会设置该版本号,表示它能完全支持的像素着色器的最高版本。

纹理操作的转换

像素着色器在以下几个方面扩展并一般化了Microsoft DirectX&reg; 6.0和7.0的多重纹理能力。

加入了一组通用读/写寄存器,这样就允许更为灵活的表达式。用D3DTA_CURRENT的连续级联需要为每层指定一个单独的结果寄存器参数。 D3DTOP_MODULATE2X和D3DTOP_MODULATE4X纹理操作被分成了单独的修饰符,可用于任何指令。这样就无需单独的D3DTOP_MODULATE和D3DTOP_MODULATE2X操作了。 乘加操作加入了可选的第三个参数,因此程序化的像素着色器可以执行arg1 ×arg2 + arg0的操作。这样就不再需要D3DTOP_MODULATEALPHA_ADDCOLOR和D3DTOP_MODULATECOLOR_ADDALPHA 纹理操作了。 混合操作加入了可选的第三个参数,因此程序化的像素着色器可以用arg0作为arg1和arg2之间的混合比。这样就不再需要D3DTOP_BLENDDIFFUSEALPHA, D3DTOP_BLENDTEXTUREALPHA, D3DTOP_BLENDFACTORALPHA, D3DTOP_BLENDTEXTUREALPHAPM, 和D3DTOP_BLENDCURRENTALPHA纹理操作了。 对纹理寻址的修改操作,如D3DTOP_BUMPENVMAP,被从颜色和阿尔法操作中分离出来并作为第三种操作类型,专门用于纹理寻址操作。 为了有效地支持这种新增的灵活性,API的语法从DWORD对改成了ASCII汇编代码语法。这样就暴露了程序化的像素着色器所提供的功能。

注意在使用像素着色器时,镜面反射加法不专门由一个渲染状态控制,如果需要,这可能由像素着色器实现。但是,雾混合仍然由固定功能流水线执行。

对纹理的一些考虑

像素着色器完全取代了由Microsoft DirectX&reg; 6.0和7.0的多重纹理API提供的像素混合功能,尤其是那些由D3DTSS_COLOROP, D3DTSS_COLORARG1, D3DTSS_COLORARG2, D3DTSS_ALPHAOP, D3DTSS_ALPHAARG1和D3DTSS_ALPHAARG2纹理层状态、相关的参数和修饰符定义的操作。如果设置了程序化的像素着色器,那么这些状态会被忽略。

纹理层和取样器状态

如果像素着色器正在运行,那么以下纹理层状态仍然会被使用。对这些状态的使用取决于像素着色器的版本,如下表所示。

纹理层状态 版本

D3DTSS_BUMPENVMAT00 1_1 - 1_4

D3DTSS_BUMPENVMAT01 1_1 - 1_4

D3DTSS_BUMPENVMAT10 1_1 - 1_4

D3DTSS_BUMPENVMAT11 1_1 - 1_4

D3DTSS_BUMPENVLSCALE 1_1 - 1_3

D3DTSS_BUMPENVLOFFSET 1_1 - 1_3

D3DTSS_TEXCOORDINDEX 1_1 - 1_3. 仅对固定功能顶点处理有效。

D3DTSS_TEXTURETRANSFORMFLAGS 1_1 - 1_3. 仅对固定功能顶点处理有效。如果像素着色器正在运行,那么下面的取样器状态仍然会被使用。

取样器状态 版本

D3DSAMP_ADDRESSU 所有版本

D3DSAMP_ADDRESSV 所有版本

D3DSAMP_ADDRESSW 所有版本

D3DSAMP_BORDERCOLOR 所有版本

D3DSAMP_MAGFILTER 所有版本

D3DSAMP_MINFILTER 所有版本

D3DSAMP_MIPFILTER 所有版本

D3DSAMP_MIPMAPLODBIAS 所有版本

D3DSAMP_MAXMIPLEVEL 所有版本

D3DSAMP_MAXANISOTROPY 所有版本

D3DSAMP_SRGBTEXTURE 所有版本

D3DSAMP_ELEMENTINDEX 所有版本

D3DSAMP_DMAPOFFSET 仅顶点着色器(位移贴图)因为纹理层状态不是像素着色器的一部分,在编译着色器时它们是不可用的,所以驱动程序无法对它们做任何假定。例如,驱动程序不能在那时区分出双线性过滤和三线性过滤。应用程序可以自由地改变这些状态而无需重新生成当前绑定的着色器。

像素着色器和纹理取样

纹理取样和过滤操作由标准纹理层状态中的放大、缩小、mip过滤、以及环绕寻址模式控制。更多信息,请参阅纹理层状态。因为驱动程序在编译时也无法得到这些信息,所以着色器必须能够在这些状态改变后继续运行。应用程序负责设置像素着色器所需的正确类型的纹理(二维贴图,立方体贴图,立体贴图等)。如果设置不正确的纹理类型,那么将会产生不可预料的结果。

在像素着色器之后的处理

其它一些像素操作——如雾混合、模板操作、以及渲染目标混合——在着色器执行以后发生。为了支持本主题描述的新特性,渲染目标混合的语法已经做了更新。

像素着色器的输入

对像素着色器版本1.1到2.0来说,漫反射色和镜面反射色在给着色器使用之前被截取到范围0到1之间,因为这是着色器的有效输入范围。

输入到像素着色器的颜色值被认为是经过透视校正的,但并非所有硬件都能保证这一点。寻址处理器根据纹理坐标产生的颜色总是以透视校正的方式进行迭代。但是,在迭代过程中,它们也会被截取到范围0到1之间。

像素着色器输出

对像素着色器版本1.1到1.4来说,像素着色器产生的输出是寄存器r0的内容。该寄存器的值会在着色器处理结束后被送往雾处理阶段和渲染目标混合器。

对像素着色器版本2.0以上来说,输出的颜色值为oC0到oC4。

像素着色器示例

本节包含三个像素着色器示例。每个示例都建立在前一个示例的基础上,并增加一些功能。

应用纹理贴图 把顶点漫反射色和纹理进行混合 用颜色值把两张纹理进行混合

应用纹理贴图

本示例把一张纹理贴图应用于一个四边形。本例和前例的区别在于:

顶点数据结构和FVF码包含了纹理坐标。顶点数据包含了u, v数据。因为像素的颜色将从纹理贴图得到,所以顶点数据不再需要漫反射色。 用IDirect3DDevice9::SetTexture把纹理连接到纹理层0。 着色器用t0纹理寄存器取代v0漫反射色寄存器。 示例代码如下:

// 定义顶点数据结构。

struct CUSTOMVERTEX

{

FLOAT x, y, z;

FLOAT u1, v1;

};

// 定义相应的FVF码。

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_TEX|D3DFVF_TEXCOORDSIZE2(0))

// 创建包含位置和纹理坐标的顶点数据。

static CUSTOMVERTEX g_Vertices[]=

{

// x y z u1 v1

{ -1.0f, -1.0f, 0.0f, 0, 1, },

{ 1.0f, -1.0f, 0.0f, 1, 1, },

{ 1.0f, 1.0f, 0.0f, 1, 0, },

{ -1.0f, 1.0f, 0.0f, 0, 0, },

// 为了和Windows从上到下的约定一致,v1被颠倒了。

// 左上角的纹理坐标为(0,0)。

// 右下角的纹理坐标为(1,1)。

};

// 创建纹理。这个文件包含在供下载的DirectX 9.0 SDK的媒体文件中。

TCHAR strTexturePath[512];

DXUtil_FindMediaFileCb( strTexturePath, sizeof(strTexturePath),

_T("DX5_Logo.bmp") );

LPDIRECT3DTEXTURE9 m_pTexture0;

D3DUtil_CreateTexture( m_pd3dDevice, strTexturePath,

&m_pTexture0, D3DFMT_R5G6B5 );

// Create the pixel shader.

TCHAR strShaderPath[512];

DXUtil_FindMediaFileCb( strShaderPath, sizeof(strShaderPath),

_T("PixelShader2.txt") );

这个函数是示例框架使用的一个辅助函数,许多示例都以它为基础。

LPD3DXBUFFER pCode; // 用来存放经过汇编的着色器代码的缓存

LPD3DXBUFFER pErrorMsgs; // 用来存放错误信息的缓存

LPDIRECT3DPIXELSHADER9 m_pPixelShader;

D3DXAssembleShaderFromFile( strShaderPath, 0, NULL, &pCode, NULL );

m_pd3dDevice->CreatePixelShader( (DWORD*)pCode->GetBufferPointer(),

&m_pPixelShader );

m_pd3dDevice->SetPixelShader( m_pPixelShader );

// 载入纹理并渲染输出像素。

m_pd3dDevice->SetTexture( 0, m_pTexture0 );

m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );

// 文件"PixelShader2.txt"的内容

// 把纹理贴图应用于物体的顶点。

ps_1_1 // 文件的第一条必须是版本指令。

tex t0 // 声明纹理寄存器t0,从纹理层0载入。

mov r0, t0 // 把纹理寄存器数据(t0)复制到输出寄存器(r0)。

得到的图像如下面所示。

把顶点漫反射色和纹理进行混合

本例把纹理贴图中的颜色与顶点的颜色进行混合。本例与前例的区别如下:

顶点的数据结构,FVF码和顶点数据包含了漫反射色。 着色器文件用乘法(mul)指令把纹理的颜色和顶点的漫反射色进行混合。 创建纹理和载入纹理的代码是相同的,放在这里是为了保持代码的完整性。

struct CUSTOMVERTEX

{

FLOAT x, y, z;

DWORD color1;

FLOAT tu1, tv1;

};

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1|

D3DFVF_TEXCOORDSIZE2(0))

static CUSTOMVERTEX g_Vertices[]=

{

// x y z diffuse u1 v1

{ -1.0f, -1.0f, 0.0f, 0xffff0000, 0, 1, }, // 红

{ 1.0f, -1.0f, 0.0f, 0xff00ff00, 1, 1, }, // 绿

{ 1.0f, 1.0f, 0.0f, 0xff0000ff, 1, 0, }, // 蓝

{ -1.0f, 1.0f, 0.0f, 0xffffffff, 0, 0, }, // 白

// 为了和Windows从上到下的约定一致,v1被颠倒了。

// 左上角的纹理坐标为(0,0)。

// 右下角的纹理坐标为(1,1)。

};

// 创建纹理。这个文件包含在供下载的DirectX 9.0 SDK的媒体文件中。

TCHAR strTexturePath[512];

DXUtil_FindMediaFileCb( strTexturePath, sizeof(strTexturePath),

_T("DX5_Logo.bmp") );

LPDIRECT3DTEXTURE9 m_pTexture0;

D3DUtil_CreateTexture( m_pd3dDevice, strTexturePath,

&m_pTexture0, D3DFMT_R5G6B5 );

// 创建像素着色器。

TCHAR strShaderPath[512];

DXUtil_FindMediaFileCb( strShaderPath, sizeof(strShaderPath),

_T("PixelShader3.txt") );

LPD3DXBUFFER pCode; // 用来存放经过汇编的着色器代码的缓存

LPD3DXBUFFER pErrorMsgs; // 用来存放错误信息的缓存

LPDIRECT3DPIXELSHADER9 m_pPixelShader;

D3DXAssembleShaderFromFile( strShaderPath, 0, NULL, &pCode, NULL );

m_pd3dDevice->CreatePixelShader( (DWORD*)pCode->GetBufferPointer(),

&m_pPixelShader );

m_pd3dDevice->SetPixelShader( m_pPixelShader );

// 载入纹理并渲染输出像素。

m_pd3dDevice->SetTexture( 0, m_pTexture0 );

m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );

// 文件"PixelShader3.txt"的内容

ps_1_1 // 版本指令

tex t0 // 声明纹理寄存器t0, 从纹理层0载入

mul r0, v0, t0 // v0*t0, 然后复制到r0

着色器的输入如下面所示。第一幅图为顶点颜色,第二幅图为纹理贴图。

得到的图像如下面所示,也就是顶点的颜色和纹理贴图混合的结果。

用颜色值把两张纹理进行混合

本例把两张纹理贴图进行混合,顶点的颜色用来决定每张纹理贴图的颜色所占的比例。本例和前例的区别如下:

因为使用了两张纹理,所以顶点的数据结构,FVF码,以及顶点数据包含了第二组纹理坐标。另外IDirect3DDevice9::SetTexture也调用了两次,并设置两个纹理层的状态。 着色器文件声明了两个纹理寄存器并使用线性插值(lrp)指令把两张纹理进行混合。漫反射色的值用来决定两张纹理在输出的颜色中所占的比例。 下面是示例代码。

struct CUSTOMVERTEX

{

FLOAT x, y, z;

DWORD color;

FLOAT tu1, tv1;

FLOAT tu2, tv2; // 第二组纹理坐标

};

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3D_FVF_DIFFUSE|D3DFVF_TEX2|

D3DFVF_TEXCOORDSIZE4(0))

static CUSTOMVERTEX g_Vertices[]=

{

// x y z color u1 v1 u2 v2

{ -1.0f, -1.0f, 0.0f, 0xff0000ff, 1.0f, 1.0f, 1.0f, 1.0f },

{ +1.0f, -1.0f, 0.0f, 0xffff0000, 0.0f, 1.0f, 0.0f, 1.0f },

{ +1.0f, +1.0f, 0.0f, 0xffffff00, 0.0f, 0.0f, 0.0f, 0.0f },

{ -1.0f, +1.0f, 0.0f, 0xffffffff, 1.0f, 0.0f, 1.0f, 0.0f },

};

// 创建纹理。这个文件包含在供下载的DirectX 9.0 SDK的媒体文件中。

TCHAR strTexturePath[512];

LPDIRECT3DTEXTURE9 m_pTexture0, m_pTexture1;

DXUtil_FindMediaFileCb( strTexturePath, sizeof(strTexturePath),

_T("DX5_Logo.bmp") );

D3DUtil_CreateTexture( m_pd3dDevice, strTexturePath,

&m_pTexture0, D3DFMT_R5G6B5 );

DXUtil_FindMediaFileCb( strTexturePath, sizeof(strTexturePath),

_T("snow2.jpg") );

D3DUtil_CreateTexture( m_pd3dDevice, strTexturePath,

&m_pTexture0, D3DFMT_R5G6B5 );

// 创建像素着色器。

TCHAR strShaderPath[512];

DXUtil_FindMediaFileCb( strShaderPath, sizeof(strShaderPath),

_T("PixelShader4.txt") );

LPD3DXBUFFER pCode; // 用来存放经过汇编的着色器代码的缓存

LPD3DXBUFFER pErrorMsgs; // 用来存放错误信息的缓存

LPDIRECT3DPIXELSHADER9 m_pPixelShader;

D3DXAssembleShaderFromFile( strShaderPath, 0, NULL, &pCode, NULL );

m_pd3dDevice->CreatePixelShader( (DWORD*)pCode->GetBufferPointer(),

&m_pPixelShader );

m_pd3dDevice->SetPixelShader( m_pPixelShader );

// Load the textures stages.

m_pd3dDevice->SetTexture( 0, m_pTexture0 );

m_pd3dDevice->SetTexture( 1, m_pTexture1 ); // 使用第二层纹理

m_pd3dDevice->SetStreamSource( 0, m_pQuadVB, sizeof(CUSTOMVERTEX) );

m_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );

m_pd3dDevice->SetPixelShader( m_pPixelShader );

m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );

// 文件"PixelShader4.txt"的内容

ps_1_1 // 像素着色器版本。

tex t0 // 纹理寄存器t0从第0层载入。

tex t1 // 纹理寄存器t1从第1层载入。

mov r1, t1 // 把纹理t1复制到输出寄存器r1。

lrp r0, v0, t0, r1 // 用v0中指定的比例在t0和r1间进行线性插值。

得到的图像如下:

调试

用Visual Studio进行调试

Microsoft Visual Studio 存在一个扩展以支持对某些类型的顶点着色器进行调试。更多信息请参阅着色器调试器。

其它着色器调试器

一些图形芯片厂商在他们的网站提供着色器调试工具。可以通过查找互联网或阅读下面的文章找到这些工具。读者可以在程序运行的过程中把调试器连上去,并用调试器单步运行着色器。通过设置断点,读者可以一次执行一行着色器代码并观察寄存器状态的变化。有关顶点着色器和调试技巧的更多信息。

对纹理混合进行调试

另一个示例程序MFC Tex Sample是SDK安装的组成部分。这个MFC程序是学习如何在固定功能流水线中进行多重纹理混合操作的一个不错的方法。

随便看

 

百科全书收录4421916条中文百科知识,基本涵盖了大多数领域的百科知识,是一部内容开放、自由的电子版百科全书。

 

Copyright © 2004-2023 Cnenc.net All Rights Reserved
更新时间:2025/3/1 23:18:01