- Recoards 记录
- 图元光栅
- Bitmap.SetPixel优化成LockBits/UnlockBits指针操作
- Blend
- Projection 投影
- Wireframe 线框
- Scissor 矩形绘制区域剔除
- AlphaTest alpha测试剔除
- Cull 面向剔除
- Cull的枚举
- FrontFace枚举
- Camera 相机封装
- GameObject
- Mesh
- Camera正交投影
- 深度测试、ShadingMode
- 深度
- ShadingMode
- 光照
- DepthOffset 深度偏移
- Programmable Piepine Shader 可编程管线阶段的着色器
- VertexShader 顶点着色器
- 类定义VSAttribute、继承ShaderBase
- 着色器名称、哈希码
- Uniform 数据
- 类似Uniform block的BaseShaderData
- InAttribute的输入数据对象
- Out输出数据对象
- 着色器Main函数
- FragmentShader 片段着色器
- 运行效果
- Shader Passes 没去封装
- Texture Perspective Mapping 纹理投影映射
- 步骤:
- 解决纹理投影校正的问题
- Texture Wrap Mode 纹理包裹模式
- 外部设置纹理
- FragmentShader添加采样处理
- Clamp
- Repeat
- Mirror
- MirrorOnce
- RepeatX | MirrorY
- Sampler2D - WrapMode 的算法
- WrapMode 主要是Mirror的需要讲一下
- Normal、Tangent 法线、切线
- Ambient、Diffuse、Specular 环境光、漫反射、高光
- 运行效果
- ShowNormalLines 显示法线
- ShowTBN 将ShowNormalLines 该为显示TBN
- Camera Control 增加控制镜头的方式
- Simple pritimive clip 增加了简单的图元裁剪
- Load DIY Model *.m 加载自定义模型\*.m文件
- Export & Load Mesh.isReadable\==false 导出与加载Mesh.isReadable==false的模型
- Post-Process 添加后效处理
- AA(Anti-Aliasing) 抗锯齿
- FullScreenBlur 全屏均值模糊
- 重构项目,添加FrameBuffer
- 添加:Shader/SubShader/Pass架构
- StencilTest/Buffer 添加了模板缓存,模板测试功能
- 使用Stencil来描边
- 使用新的Pass方式来描边
- 无透视描边
- 有透视描边
- StencilBuffer/Test 添加模板缓存、测试
- 使用Stencil来描边
- 使用Stencil来遮罩
- 使用Stencil来镂空
- 矩阵验算工具
- 后续制作
- 总结
- Project
- References
目的为了了解,验证图形功能,加深基础知识。 写这个光栅渲染器过程,真的发现自己的数学知识不够。 会有挺多卡点。 不知道谁可以介绍一些超级基础数学知识的书本或是教程,或是学习方式。 关于:线性代数,几何变换,仿射,微积分,的就好,其实我自己也搜索过很多资料。 就是没找到好的资源,可能自己的基础太差,没法理解真正的几何意义。
Recoards 记录 图元光栅Start 开始 今天写的栅格器,运行效果 发现对底层还是很多不了解的,要先暂停,看一下理论知识,总结差不多,再继续写
一边学习OpenGL,一边总结写到软渲染器中。 但是性能不用考虑,因为我只渲染一帧。
法线渲染一帧都挺卡的(-_-!),特别是片段生成多的时候,更加卡。。。
Bitmap.SetPixel优化成LockBits/UnlockBits指针操作2019.07.16 优化了Bitmap的像素设置方式。 才发现底层有更好的接口来画画布。 下面是设置了一个Timer.Interval = 15ms、10ms、1ms都试过。FPS都不变。 然后我将,Draw的代码屏蔽掉,结果FPS还是一样。 估计的Timer内部的Tick事件限制频率了。
2019.07.17 添加了Blend 片段多了,性能还是上不去。-_-!
上面的GIF是旧的,因为之前没发现BitmapData的颜色指针的数据中,是BGRA而不是RGBA。 所以混合效果有些不太一样,这个问题就修复。
2019.07.17添加了透视相机、混合优化 投影效果: 渲染管线中的将几何3D点集统一经过一系列的变换流程,最终生成2D窗口坐标。 以下是变换顺序:
- Object Space Coordinates - Application to vertex data // 这时是没有任何变换的局部模型坐标系,
- Clip Space Coordinates - Geometry // 有多个阶段
- VertexShader // 这一般由VertexShader负责逐顶点的变换,这里的变换一般是先传入来的Object Space Coordinates,然后根据用户的可编程变换到对应的空间,这儿我们就变换到Clip Space Coordinates,所以,在这个阶段我们需要MVP(Model & View & Project)矩阵,需要在Application 在对shader的uniform变量设置
- MVP = M a t r i x p r o j e c t ⋅ M a t r i x v i e w ⋅ M a t r i x m o d e l / w o r l d Matrix_{project} \cdot Matrix_{view} \cdot Matrix_{model/world} Matrixproject⋅Matrixview⋅Matrixmodel/world组合成的
- Clip Space Pos = M V P ⋅ O b j e c t S p a c e P o s MVP \cdot Object Space Pos MVP⋅ObjectSpacePos
- VertexShader // 这一般由VertexShader负责逐顶点的变换,这里的变换一般是先传入来的Object Space Coordinates,然后根据用户的可编程变换到对应的空间,这儿我们就变换到Clip Space Coordinates,所以,在这个阶段我们需要MVP(Model & View & Project)矩阵,需要在Application 在对shader的uniform变量设置
- NDC, Window Coordinates - VertexShader Post-Processing // 是VertexShader后处理阶段,一般就是Primitive Assembly,会对图元生成(根据你指定的是Point,还是Line,Triange等),对其剪切(我没处理这个),(Tessellation, Geometry Shader先不管),然后再将 x y z w n d c = x y z w c l i p / w c l i p xyzw_{ndc}=xyzw_{clip}/w_{clip} xyzwndc=xyzwclip/wclip,再见 x y z w n d c xyzw_{ndc} xyzwndc转到 x y z w i n d o w xyz_{window} xyzwindow,ndc的xyz都是[-1,1]的数值范围了,映射到window的viewport=x,y,width,height,比较简单: x w i n = x v i e w p o r t + ( x n d c ∗ 0.5 + 0.5 ) ∗ w i d t h v i e w p o r t ; y w i n = y v i e w p o r t + ( y n d c ∗ 0.5 + 0.5 ) ∗ h e i g h t v i e w p o r t ; z w i n = ( z n d c − n e a r ) / ( f a r − n e a r ) ; x_{win}=x_{viewport}+(x_{ndc}*0.5+0.5)*width_{viewport};\\y_{win}=y_{viewport}+(y_{ndc}*0.5+0.5)*height_{viewport};\\z_{win}=(z_{ndc}-near)/(far-near); xwin=xviewport+(xndc∗0.5+0.5)∗widthviewport;ywin=yviewport+(yndc∗0.5+0.5)∗heightviewport;zwin=(zndc−near)/(far−near); 也可参考之前写的:https://blog.csdn.net/linjf520/article/details/95770635
下面就是最终在窗口的坐标(Window Coordinates)
后面重构成:ShadingMode了 类似的,后面还会再添加一个:WifreframeType,可以是Point、Line,两种。
Scissor 矩形绘制区域剔除这个是对光栅的像素做剔除用的,就设置一个矩形(x,y,width,height)
我看很多资料教程都是将这个裁剪矩形片段剔除都是再片段着色器后处理的,不知道为何这样做。 而我的软渲染器里,是放在片段着色器前,即:光栅插值出片段的x,y坐标时,就会去判断裁剪矩形剔除。
AlphaTest alpha测试剔除一般AlphaTest是在Scissor会后,对着色之后的片段alpha值进行AlphaTestComp的比较模式来确定剔除关系。
在我的软渲染器里,我是放在片段着色器后,再对alpha测试,因为片段着色器会有可能对片段的alpha值做修改。
Cull 面向剔除添加了个Cube,先在还没加深度测试,所以在下面的GIF可以看到我在切换Cull为Off时,会有一些背面的信息画在了正面上
Cull的枚举- Back - 背面,这是默认项
- Front - 正面
- Off 不剔除
- Clock 顺时针 默认(这儿与Unity一样,OpenGL默认的是逆时针)
- CounterClock 逆时针
简单写了个类似Unity的Camera属性的封装类。 在Camera属性中可以调整对应的属性。 属性分类:
- look at 是需要lookat时的数据
- proj-both 是透视与正交的两者公共属性
- proj-ortho 是正交的属性
- proj-perspective 是透视的数字那个
- transform 是该Camera的视图矩阵、投影矩阵,可以对封装的GameObject对象使用变换
- view 是视图矩阵相关的参数
- Viewport 是窗口坐标映射,将NDC坐标映射到Window Coordinates映射中的x,y偏移与w,h宽高
封装了一个简单易用的GameObject,没去写Component系统。 因为验证渲染功能,后续有空可完善一下
Mesh简单封装了Mesh对象,有点类似Unity的Mesh也是包含一下内容
- vertices[] 顶点(现在有用到)
- triangles[] 三角形索引(也叫indices,有用到)
- uvs[] 采样纹理时使用的uv坐标,暂时没用到
- colors[] 顶点颜色,暂时没用到
- normals[] 法线,暂时没用到(现在暂时是使用到三角面的法线,而不是顶点法线,顶点法线还要除了挺多东西的,特别是需要处理共享到多个面在的顶点的法线值,你需要处理法线的角度混合权重)
- tangents[] 切线,暂时没用到(后续处理纹理空间坐标是需要使用,如切线空间的法线贴图,等)
2019.07.19,这些天有事,断断续续的思路,干脆过一天再写,添加了深度测试
深度在下面两Cube种,的交错的位置,如果没有深度测试的话,那么效果看起来还是2D的绘制顺序一样,有了深度测试后,我们就只要管理绘制种类队列就可以了。
我这儿的深度测试类似Early-Z,在FragmentShader之前就测试了,我这儿是再片段生成的z深度值时,就立马判断深度测试。不通过的都标记一下discard丢弃掉。 后面再封装:如果使用了Early-Z就不会在走正常的FragmentShader后的DepthTest。 这里更正一下,Early-Z其实不是这样的,它的思想是:想用一个简单的vs与fs,将不透明物体的深度先绘制到深度缓存,然后在会到正常的渲染流程,这样一来,正常的渲染流程中,凡是深度不等于当前深度缓存的值,都直接剔除片段,好处就是不用处理深度上看不到片段,节省fs的计算量
但目前我的深度写入,不是标准值,后面可以参考:LearnOpenGL-CN:深度值精度
D e p t h V i e w = > D e p t h S c r e e n Depth_{View} => Depth_{Screen} DepthView=>DepthScreen 视空间深度 转化到 屏幕空间深度的公式如下: a = F / ( F − N ) a = F / (F - N) a=F/(F−N) b = N F / ( N − F ) b = NF / (N - F) b=NF/(N−F) d e p t h 屏 幕 空 间 = ( a Z + b ) / Z 为 视 空 间 深 度 depth_{屏幕空间} = (aZ + b)/ Z_{为视空间深度} depth屏幕空间=(aZ+b)/Z为视空间深度 d e p t h = ( a Z + b ) / Z depth = (aZ + b) / Z depth=(aZ+b)/Z
D e p t h S c r e e n = > D e p t h V i e w Depth_{Screen} => Depth_{View} DepthScreen=>DepthView 反推得 屏幕空间深度 转化到 视图空间深度的等于以下公式: d e p t h = ( a Z + b ) / Z depth = (aZ + b) / Z depth=(aZ+b)/Z d e p t h = a + b / Z depth = a + b / Z depth=a+b/Z d e p t h − a = b / Z depth - a = b / Z depth−a=b/Z ( d e p t h − a ) / b = 1 / Z (depth - a) / b = 1 / Z (depth−a)/b=1/Z b / ( d e p t h − a ) = Z b / (depth - a) = Z b/(depth−a)=Z
D e p t h S c r e e n = > D e p t h V i e w Depth_{Screen} => Depth_{View} DepthScreen=>DepthView 公式: b / ( d e p t h − a ) = Z b / (depth - a) = Z b/(depth−a)=Z,代入a,b: ( N F / ( N − F ) / ( d e p t h − ( F / ( F − N ) ) ) = Z (NF / (N - F) / (depth - (F / (F - N))) = Z (NF/(N−F)/(depth−(F/(F−N)))=Z
假设N=0.3, F=1000,代入N,F: ((0.3 * 1000) / (0.3 - 1000)) / (depth - (1000 / (1000 - 0.3))) = Z (300 / (-999.7)) / (depth - (1000 / 999.7)) = Z -0.3000900270081024 / (depth - 1.000300090027008)= Z
假设depth = 0.5,代入depth -0.3000900270081024 / (0.5 - 1.000300090027008) = Z -0.3000900270081024 / -0.500300090027008 = Z 0.5998200539838049 = Z 当depth(屏幕空间) = 0.5,Z(Z为视空间深度) = 0.5998200539838049 那么屏幕空间的depth就是要输入缓存的值。
ShadingMode这个设计与Unity差不多。
- Shaded 就是普通着色
- Wireframe 线框模式
- ShadedAndWireframe 就是既有着色,又有线框
2019.07.19 简单添加了方向光光照,我们下面还显示法线的调试功能,便于调试光照 下面是添加了半兰伯特光照LightDotNormal*0.5+0.5 现在我是直接添加到逐像素处理的硬编码
后续我会再添加一个可编程的VertexShader与FragmentShader 然后我会将VertexData数据都封装到Mesh类中,每帧都计算好对应Shader需要用的数据并上传到Renderer中(对于OpenGL或Dx或Vulkan就是上传到Graphicis hardware图形硬件中,即:显卡),以供后面的Shader阶段使用
类似Unity中的ShaderLab里的Offset [factor, unit]
- DepthOffset On/Off控制启用选项
- DepthOffsetFactor 控制Offset的factor因子值
- DepthOffsetUnit 控制Offset的Unit值
DepthOffset一般用于解决z-fighting。 z-fighting是由于浮点数据精度有限导致的,因为不同多边形,同再同一平面(共面)图元绘制时写入深度缓存的浮点精度误差不同导致的。 这个我之前看到好像是Nv有优化方案,就是硬件会处理同平面斜率的数据采样保存,这样不同图元的深度值的精度至少是一样的。
下面颜色如何解决z-fighting。 先来制造z-fighting,如下图 解决方案就是类似Unity ShaderLab中的Offset -1, -1 对于我们自己封装的使用方式类似,三行代码就OK了
renderer.State.DepthOffset = DepthOffset.On;
renderer.State.DepthOffsetFactor = -1;
renderer.State.DepthOffsetUnit = -1;
效果如下图
不过DepthOffset方式也有问题的,会影响后续的深度比较的精准度,特别是一些图元交错在一起时。 DepthOffset了解:
- https://blog.csdn.net/linjf520/article/details/94596104
- https://blog.csdn.net/linjf520/article/details/94596764
(其他的功能正在添加中,因为对OpenGL不熟悉,在一边看书,一边写程序)
Programmable Piepine Shader 可编程管线阶段的着色器2019.07.23更新添加了:可编程管线的VertexShader、FragmentShader(还有很多其他的功能,后续再介绍吧) 可编程阶段,花了一些时间来设计,使用C# 反射机制来实现。 可动态加载,dll来实现shader,目前我只实现了VertexShader、FragmentShader。
这次加了可编程着色器后,帧数大大降低,但我们可以将镜头拉远,让片段少一些,稍微还是可以看到效果的。可见GPU帮CPU分担了多少计算量。
特别是GPU的,纵向管线,与横向并行,都是CPU无法替代的。
但这儿是以研究为目的而设计。
先看看一个着色器代码,我将着色器代码放到了另一个类库项目,编译成.dll。 然后共主工程运行时,实时加载(其实我也可以使用C#的编译器实时编译一段C#为bytes之类的,这样就可以把C#当作脚本来加载,并编译了)到Assembly。
先看看完成的着色器代码,包含了顶点、片段着色器。
// jave.lin 2019.07.21
using RendererCommon.SoftRenderer.Common.Attributes;
using RendererCommon.SoftRenderer.Common.Shader;
using SoftRenderer.Common.Mathes;
namespace SoftRendererShader
{
[VS]
public class VertexShader : ShaderBase
{
[Name] public static readonly string Name = "MyTestVSShader";
[NameHash] public static readonly int NameHash = NameUtil.HashID(Name);
/* ==========Uniform======== */
[Uniform] public Matrix4x4 MVP;
[Uniform] public Matrix4x4 M;
/* ==========In======== */
[In] [Position] public Vector4 inPos;
[In] [Texcoord] public Vector2 inUV;
[In] [Color] public ColorNormalized inColor;
//[In] [Normal] public Vector3 inNormal;
/* ==========Out======== */
[Out] [SV_Position] public Vector4 outPos;
[Out] [Texcoord] public Vector2 outUV;
[Out] [Color] public ColorNormalized outColor;
//[Out] [Normal] public Vector3 outNormal;
public VertexShader(BasicShaderData data) : base(data)
{
}
[Main]
public override void Main()
{
var shaderData = Data as ShaderData;
outPos = MVP * inPos;
outUV = inUV;
outColor = inColor;
//outNormal = inNormal;
}
}
[FS]
public class FragmentShader : ShaderBase
{
[Name]
public static readonly string Name = "MyTestFSShader";
[NameHash]
public static readonly int NameHash = NameUtil.HashID(Name);
//[In] [Position] public Vector4 inPos;
[In] [Texcoord] public Vector2 inUV;
[In] [Color] public ColorNormalized inColor;
//[In] [Normal] public Vector3 inNormal;
[Out] [SV_Target] public ColorNormalized outColor;
public FragmentShader(BasicShaderData data) : base(data)
{
}
[Main]
public override void Main()
{
//1
//var shaderData = Data as ShaderData;
//
//Vector3 lightDir = shaderData.LightPos[0];
//float LdotN = Vector3.Dot(lightDir, inNormal);
tex2D(tex, uv)
//
//outColor = inColor * LdotN;
//2
outColor = inColor;
//outColor = new ColorNormalized(inUV.x, inUV.y, 0, 1);
}
}
}
再来详细说明。 下面是顶点着色器的示例。
VertexShader 顶点着色器 类定义VSAttribute、继承ShaderBase [VS] public class VertexShader : ShaderBase
首先是类定义加了一个VSAttribute。 标识这个类是一个顶点着色器。
你也在该shader.dll工程多添加几个VS的类,共主工程GameObject的Material(没错,我有简单的写了个Material)切换,调用不同的Shader。
着色器名称、哈希码 [Name] public static readonly string Name = "MyTestVSShader";
[NameHash] public static readonly int NameHash = NameUtil.HashID(Name);
这两个值分别是在Shader.dll加载后到ShaderLoaderMgr加载器后,外部可以在ShaderLoaderMgr.Create(shaderName)或是Create(shaderHash)的方式来创建Shader对象。
然后ShaderProgram对象在.SetShader(ShaderType.VertexShader, 你的Shader对象)即可。
外部调用,如下代码:
renderer.ShaderData = shaderData = new ShaderData(1);
renderer.ShaderMgr.Load("Shaders/SoftRendererShader.dll");
var vs_shaderName = "MyTestVSShader";
var vs_shaderHash = vs_shaderName.GetHashCode();
var fs_shaderName = "MyTestFSShader";
var fs_shaderHash = fs_shaderName.GetHashCode();
var vsShader = renderer.ShaderMgr.CreateShader(vs_shaderHash);
var fsShader = renderer.ShaderMgr.CreateShader(fs_shaderHash);
gameObjs[0].Material = new Material(vsShader, fsShader);
gameObjs[1].Material = new Material(vsShader, fsShader);
Uniform 数据
[Uniform] public Matrix4x4 MVP;
[Uniform] public Matrix4x4 M;
使用[UniformAttribute]标记的字段,都是该shader的Uniform数据(我这儿的Uniform也是可以在shader运行中更改的,不想OpenGL之类的,你对Uniform设置了,可能都会编译不通过)。Uniform数据通常是外部传进来的多个shader之间通用的数据,这个是针对但个Shader类的数据,我们也封装了一个是多个Shader对象都可以共享访问的数据有点类似Uniform block:是构造函数传进来的BasicShaderData data数据对象。
类似Uniform block的BaseShaderData这个数据对象在外部可以使用Renderer.ShaderData来设置,也可以重置成你想要的类。
例如,我们的相机位置,灯光位置,我们都可以放这里,这样多个shader之间都可以拿到这些全局的共享数据。
InAttribute的输入数据对象 [In] [Position] public Vector4 inPos;
[In] [Texcoord] public Vector2 inUV;
[In] [Color] public ColorNormalized inColor;
//[In] [Normal] public Vector3 inNormal;
我将Normal法线的字段注释了。 因为我还没在MeshRenderer(我又简单的写了个类)中实时将法线、切线计算并传如到顶点缓存(--!没错,我又写了个VertexBuffer,还有IndexBuffer,总之写了好多个类,--!)
除了法线没有传进来,我将Positionn得坐标,还有Texcoord的纹理坐标和Color颜色都传进来了。
Out输出数据对象 [Out] [SV_Position] public Vector4 outPos;
[Out] [Texcoord] public Vector2 outUV;
[Out] [Color] public ColorNormalized outColor;
有输入,还得有输出数据。 同样的我也写了个SV_Position,Texcoord,Color的三个数据输出。
SV_Position是给PrimitiveAssembly阶段使用的,PrimitiveAssemly我没有封装类,因为比较简单,就是对IndexBuffer的遍历,取到对应顶点,组合成对应PolygonMode的图元。
有了图元,接下来,就是将图元光栅化,Texcoord,Color是在Rasterizer(这个类好早之前有了,不过也是重构最多次的类)光栅器中插值用的数据。(深度值是内部固有的插值数据,所以我是不在顶点着色器公开的,但在片段阶段中后期我会封装一下,可以在片段阶段访问到深度值)
最后是我们的Main函数
着色器Main函数所有着色器都有Main函数,而且需要给Main函数添加[Main]的Attribute。
如果一个顶点着色器没有Main函数(基类不算,我的加载器有判断),或没有[Out][SV_Position]的输出数据,ShaderLoaderMgr都会报错提示的。还有同一个字段的Attribute也不能乱加,如,有个字段加了[In]了,这时你又加上了[Out],ShaderLoaderMgr也会在加载时提示报错。
Main函数就是我们主要的运算逻辑了。
[Main]
public override void Main()
{
outPos = MVP * inPos;
outUV = inUV;
outColor = inColor;
//outNormal = inNormal;
}
上面的Main函数我们可以看到非常简单。 只是将对象空间下的inPos变换到裁剪空间下的outPos。 其他量个纹理坐标,与颜色就是直接赋值就完了。
另外,我们的Attribute定义都可以加上一个数字,类似OpenGL中的布局限定符的location值。
目前只有以下这几个Attribute是有location值的:(所有location都是0~7的值,意思最多8个寄存器的概念)
- [Color(location)]
- [Texcoord(location)]
- [Normal(location)]
- [SV_Target(location)]
可以看到我们之类的Shader中的Color、Texcoord都没加上location的定义,所以默认就是0的location值。
SV_Target的location控制片段着色器输出到那个RT对象(后面再实现MRT)。其他的location是用于控制类似寄存器的区别的概念。
FragmentShader 片段着色器片段着色器的我就不多介绍了,大部分与定点着色器相同,注意一下几点:
- 类定义使用[FS]的Attribute
- 必须要有[Out][SV_Target]的输出 其他都差不多的。
在上面的代码下,运行情况是:(我使用的GIF录制软件输出的色域比较小,如果输出真彩色,那么文件过大,导致CSDN博客无法上传该GIF,所以压缩了,颜色表小(色域小),看起来就有马赛克,其实我自己电脑上运行时很平滑的颜色过渡的。) 然后我们调整一下代码,用颜色用UV值来向显示,只是调整FS中的代码即可:
[Main]
public override void Main()
{
//outColor = inColor;
outColor = new ColorNormalized(inUV.x, inUV.y, 0, 1);
}
该完代码后,记得重新编译一下(如果真的有空,我再将C#得Roslyn编辑器拿来实时将*.cs源代码文件编译成XXXShader.dll或是bytes,在加载),再用uv坐标显示RG通道,以下是运行效果:
Pass的实现还是需要写比较多的代码,虽然也是可以实现的
后续还有很多其他功能,留着以后有空再写了。
Texture Perspective Mapping 纹理投影映射2019.07.27,但是效果失败了,如下描述 我按照了《3D游戏与计算机图形学的中数学方法》的第66~68页的内容处理了。 也参考了一下的连接:
-
透视校正插值(Perspective-Correct Interpolation)
-
透视下的插值(Perspective-Correct Interpolation)
-
透视校正插值
-
3D图形学学习总结(十)—纹理映射透视矫正 - 这个博客直接推导公式,可以参考里头的原理
-
只看Perspective Correct Texture Mapping部分 - 未尝试
-
3D 图形光栅化的透视校正问题 - 未看完
- ClipPos 2 NDC Pos时,我将ClipPos.w存到了NDC.w中
- 然后NDC 2 Window Pos时,再将WindowPos.w = NDCPos.w
- 上面保留的WindowPos.w用来在生成片段是对扫描leftFrag到rightFrag生成片段的顶点输出数据透视校正插值处理:
- var invZ0 = 1 / leftFrag.pos.w;
- var invZ1 = 1 / rightFrag.pos.w;
- 然后在新生成的片段的z求出来,interpolatedFrag.z = 1 / Mathf.lerp(invZ0, invZ1, t);
- 然后对所有的顶点属性,我们重点是对UV属性插值,例如:uv = interpolatedFrag.z * Mathhf.lerp(leftFrag.uv * invZ0, rightFrag.uv * invZ1, t);
- 但是遗憾的是,结果还是不对,希望能有大神指点一下,如下效果图:
为了测试,使用了修改FragmentShader的方式来生成程序纹理的横条纹理,这样就可以测试纹理透视校正映射到底有没起到作用,代码如下:
[Main]
public override void Main()
{
var v = inUV.y * 100;
var times = (int)(v / 5);
if (times % 2 == 0) outColor = ColorNormalized.red;
else outColor = ColorNormalized.green;
}
2019.08.11 我倒回来修复这个问题了,所以写了另一个更精简的渲染器,后面会更新到这个功能中
C# 实现精简版的栅格化渲染器 - 代码很精简,修复了投影校正的问题
Texture Wrap Mode 纹理包裹模式2019.07.27 还是把纹理加上吧,透视校正插值的问题,有面再处理了 在FragmentShader中需要设置Texture,Sampler暂时不提供给外部设置属性(下面演示是直接在FS中设置sampler属性的)
外部设置纹理 var tex_bmp = new Bitmap("Images/tex.jpg");
var tex = new Texture2D(tex_bmp);
fsShader.ShaderProperties.SetUniform("mainTex", tex);
FragmentShader添加采样处理
这里就没去封装TextureUnit(纹理单元,是GPU中有限的纹理处理资源之一)。 直接就在shader中声明sampler与uniform Texture2D共外部设置。 然后shader想怎么写就怎么写了。
...
[Uniform] public Texture2D mainTex;
public Sampler2D sampler;
...
[Main]
public override void Main()
{
outColor = tex2D(sampler, mainTex, inUV);
}
下面是运行效果
下面我们将UV坐标都放大一倍,这样才可以看出其他WrapMode的区别
var uvs = new Vector2[vertices.Length];
var uvScale = 2.0f;
for (int i = 0; i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?