首页 > 其他分享 >DX12 实战 法线贴图

DX12 实战 法线贴图

时间:2023-05-07 22:36:48浏览次数:40  
标签:贴图 法线 uv large DX12 Delta input array float3

前言

本篇将展示如何使用DX12 实现normal map

源代码chenglixue/D3D12 at normalmap

要点

  • 定义:法线贴图基于凹凸贴图衍生出来的。纹理贴图中的纹素是RGB颜色值,而法线贴图中的纹素是法向量的坐标

    image-20230507173600688

  • 用途:计算光照,在纹理图中存储法向量,再将其带入光照计算。在避免高模建模的情况下也可以达到理想的效果

压缩和存储法线图

由于纹理图的图像格式范围一般是[0,255],而归一化法向量的范围是[-1,1],因此将法向量存于纹理图中需要进行变换

  • 法向量转换为图像格式::\(\large f(x) = (0.5x + 0.5) * 255\)
  • 图像格式转换为法向量:\(\large f^{-1}(x) = \frac{2x}{255} - 1\)

切线空间

  • 切线空间是一个3d纹理坐标系,xy轴分别对应uv轴,z轴为法向量,且这三条轴两两相切。称T轴为切线(tangent)、B轴为副切线( binormal)、N轴为法线
    image-20230507174033343

TBN矩阵

一般来说计算光照是在世界空间中进行计算,但法线图中的法向量定义在切线空间,TBN矩阵定义了将纹理从切线空间转换至世界空间的过程

TBN矩阵仅仅关注向量的变化,因此这是个\(3\times3\)的矩阵

从uv空间到局部空间

  • 设纹理坐标分别为\(\large (u_0, v_0),(u_1, v_1),(u_2, v_2)\)的顶点\(\large v_0, v_1, v_2\)在切线空间中定义了一个三角形,\(\large e_0 = (\Delta_{u0} , \Delta v_0) = (u_1 - u_0, v_1 - v_0)\),\(\large e_1 = (\Delta_{u1} , \Delta v_1) = (u_2 - u_0, v_2 - v_0)\)

    image-20230507174923997

  • 到局部空间的坐标变化

    \(\large \left[\begin {array} {cc} e_{0,x} & e_{0,y} & e_{0,z} \\ \large e_{1,x} & e_{1,y} & e_{1,z} \end {array} \right] = \left[\begin {array} {cc} \Delta_{u_0} & \Delta_{v_0} \\ \large \Delta_{u_1} & \Delta_{v_1} \end {array} \right] \left[\begin {array} {cc} T_{x} & T_{y} & T_{z} \\ \large B_{x} & B_{y} & B_{z} \end {array} \right]\)

  • 对uv矩阵求逆

    \(\large \left[\begin {array} {cc} T_{x} & T_{y} & T_{z} \\ \large B_{x} & B_{y} & B_{z} \end {array} \right] = \large \left[\begin {array} {cc} \Delta_{u_0} & \Delta_{v_0} \\ \large \Delta_{u_1} & \Delta_{v_1} \end {array} \right]^{-1} \large \left[\begin {array} {cc} e_{0,x} & e_{0,y} & e_{0,z} \\ \large e_{1,x} & e_{1,y} & e_{1,z} \end {array} \right] \large = \frac{1}{\Delta_{u_0} \Delta{v_1} - \Delta{v_0}\Delta{u_1}} \left[\begin {array} {cc} \Delta_{v_1} & -\Delta_{v_0} \\ \large -\Delta{u_1} & \Delta_{u_0} \end {array} \right] \left[\begin {array} {cc} e_{0,x} & e_{0,y} & e_{0,z} \\ \large e_{1,x} & e_{1,y} & e_{1,z} \end {array} \right]\)

    这里用到了逆矩阵性质:矩阵\(A = \left[\begin {array} {cc} a & b \\ \large c & d \end {array} \right]\),有\(\large A^{-1} = \frac{1}{ad - bc} \left[\begin {array} {cc} d & -b \\ \large -c & a \end {array} \right]\)

平均化法线

  • 大多数情况三角形和三角形之间都会共享顶点。对于不在一个平面的三角形,需要对他们的法线进行平均化达到更加柔和的效果。但对于平行的三角形则无需平均化

流程

  • 美术创造预定的法线图并将其存为图像文件
  • D3D初始化时提取该图像文件
  • 计算每一个三角形的切向量T(平均化)
  • 在VS中,将顶点的法线和切向量变换至世界空间,并将结果输出至PS
  • 通过插值切向量和法向量来构建三角形面每个像素点处的TBN基,再将该TBN基从切线空间变换至世界空间

实现

将normal map纹理贴图绑定至管线的步骤这里就省略咯,和前面纹理贴图中导入纹理一摸一样的

inputlayout

计算TBN需要normal 和 tangent,因此需要在初始化阶段导入模型的normal 和 tangent

m_inputLayout =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
    { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
    { "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 32, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};

shader

struct VSInput
{
    float3 position : POSITION;
    float3 normal : NORMAL;
    float2 uv : TEXCOORD;
    float3 tangent : TANGENT;
};

struct PSInput
{
    float4 positionH : SV_POSITION;
    float3 positionW : POSITION;
    float3 normalW : NORMAL;
    float3 tangentW : TANGENT;
    float2 uv : TEXCOORD;
};

Texture2D g_normalmapTexture : register(t2, space0
                                        
PSInput main(VSInput input)
{
    PSInput result;

    float4 positionW = mul(float4(input.position, 1.f), world);
    
    result.positionW = positionW.xyz;
    
    result.positionH = mul(positionW, viewProjection);
    
    // normal tangent变换至世界空间
    result.normalW = mul(input.normal, (float3x3)world);
    
    result.tangentW = mul(input.tangent, (float3x3) world);

    result.uv = input.uv;

	return result;
}
                                        
float4 main(PSInput input) : SV_TARGET
{
    float4 textureDiffuseAlbedo = g_diffuseTexture.Sample(g_SamperAnisotropyWrap, input.uv);
    float4 textureSpecularAlbedo = g_specularTexture.Sample(g_SamperAnisotropyWrap, input.uv);
    // 采样 normalmap texture
    float4 normalMapSample = g_normalmapTexture.Sample(g_SamperAnisotropyWrap, input.uv);
    // 从uv空间变换至世界空间
    float3 averageNormalW = normalmapToWolrd(normalMapSample.rgb, input.normalW, input.tangentW);
    
    input.normalW = normalize(input.normalW);
    
    // 后面光照的计算法线都用averageNormalW
    float3 toEyeDirW = eyeWorldPosition - averageNormalW;
    float toEyeLength = length(toEyeDirW);
    toEyeDirW = normalize(toEyeDirW);
    
    Material material = { textureDiffuseAlbedo, textureSpecularAlbedo, ambientAlbedo, specualrShiness };

    float4 resultLightColor = CalcLightColor(lights, material, averageNormalW, toEyeDirW, input.positionW);
    
    resultLightColor.a = textureDiffuseAlbedo.a;
    
    return resultLightColor;
}

// 将法线贴图从uv空间变换至世界空间
float3 normalmapToWolrd(float3 normalmap, float3 normalizeNormalW, float3 tangentW)
{
    // [0,1] -> [-1,1]
    float3 normalConvert = 2.f * normalmap - 1.f;
    
    // build TBN
    float3 N = normalizeNormalW;
    // because after lerp T and N may not be orthogonal vectors
    float3 T = normalize(tangentW - dot(N, tangentW) * N);
    float3 B = cross(N, T);
    float3x3 TBN = float3x3(T, B, N);
    
    float3 result = mul(normalConvert, TBN);

    return result;
}                                        

“float3 T = normalize(tangentW - dot(N, tangentW) * N);”这里的原因是从uv空间变换至世界空间,normal 和 tangent可能不再互切
image-20230507221412696

输出

image-20230507220245511

标签:贴图,法线,uv,large,DX12,Delta,input,array,float3
From: https://www.cnblogs.com/chenglixue/p/17380317.html

相关文章

  • 球面的细分和贴图
    在Cesium运行的时候,会将不同层级的影像瓦片(贴图)会映射到地形瓦片(比球面更不规则的几何形状)上,这样尽管在代码上有一些复杂(例如重投影等计算),换来的是对各种开放地图服务标准的支持。这与游戏引擎有很大的不同,游戏引擎会对地形和纹理进行组合优化成自定义的格式,但是缺少了灵活......
  • DX12 实战 blend
    前言本篇将展示如何使用DX12实现blend源代码D3D12atblend要点over&under算子blend的实现是基于over算子和under算子over含义:应对半透明物体在不透明物体前或半透明物体和半透明物体间的情况,它的顺序是从后向前——从源像素(PS中输出的片元)到目标像素(缓冲区内的片元)......
  • DX12 实现 模板——物体轮廓
    前言本篇将展示如何运用深度模板缓冲区来实现游戏中的物体轮廓效果源代码model_outline基础知识模板测试过程//compare_func:定义的比较函数。对两个参数进行比较//StencilRef:模板参考值//StencilReadMask:位于D3D12_DEPTH_STENCIL_DESC//Value:正在接受模板测试的值if......
  • DX12 实现 天空盒
    前言本篇将展示如何使用DX12实现天空盒源代码cubemap创建dds立方体贴图微软官方提供了一个命令行工具Texassemble,该工具可以将输入的几个图片转换为GPU可识别的DDS贴图,如立方体贴图语法texassemble<command>[-r][-flist<filename>][-wwidth][-hheight][-f......
  • DX12 实战 BlinnPhong & 纹理贴图
    前言本篇将展示如何实现BlinnPhong光照,以及为人物模型贴上纹理对于理论不清楚的小伙伴可以看这图形学理论局部光照,[图形学理论纹理贴图](https://www.cnblogs.com/chenglixue/p/17109214.html)具体代码看这github.com材质由于漫反射率和镜面反射率我们都是从纹理图中提取,因......
  • ueditor 实现ctrl+v粘贴图片并上传、word粘贴带图片
    ​如何做到ueditor批量上传word图片?1、前端引用代码<!DOCTYPE html PUBLIC "-//W3C//DTDXHTML1.0Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head>......
  • 百度ueditor 实现ctrl+v粘贴图片并上传、word粘贴带图片
    ​ 自动导入Word图片,或者粘贴Word内容时自动上传所有的图片,并且最终保留Word样式,这应该是Web编辑器里面最基本的一个需求功能了。一般情况下我们将Word内容粘贴到Web编辑器(富文本编辑器)中时,编辑器都无法自动上传图片。需要用户手动一张张上传Word图片。如果只有一张图片还能够接......
  • 百度编辑器 实现ctrl+v粘贴图片并上传、word粘贴带图片
    ​ 百度ueditor新增的将word内容导入到富文本编辑框的功能怎么没有啊,...ueditor实现word文档的导入和下载功能的方法:1、UEditor没有提供word的导入功能,只能说是粘贴复制。2、方案:用poi来提供word导入,思路是将word转换为html输出,再用UEditor提供的setContent()方法将html的内容......
  • FCKEditor 实现ctrl+v粘贴图片并上传、word粘贴带图片
    ​图片的复制无非有两种方法,一种是图片直接上传到服务器,另外一种转换成二进制流的base64码目前限chrome浏览器使用首先以um-editor的二进制流保存为例:打开umeditor.js,找到UM.plugins['autoupload'],然后找到autoUploadHandler方法,注释掉其中的代码。加入下面的代码://判断剪贴......
  • KindEditor 实现ctrl+v粘贴图片并上传、word粘贴带图片
    ​ 这种方法是servlet,编写好在web.xml里配置servlet-class和servlet-mapping即可使用后台(服务端)java服务代码:(上传至ROOT/lqxcPics文件夹下)<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%><%@     page contentType="text/html;cha......