当前位置: 首页 > news >正文

自由学习记录(74)

能否把 ComputeGrabScreenPos(o.pos) 改为 ComputeScreenPos(o.pos)

简而言之——不可以。这两个函数虽然类似,但用途不同,不能互换。


⚙️ 两者区别简明戳表

函数用途输出是否适合 GrabPass UV是否处理 API 平台差异
ComputeScreenPos(clipPos)用于一般屏幕采样(例如在屏幕空间贴图),生成 UV 坐标。❌ 不可靠✅ 处理 NDC 但不考虑 Flip
ComputeGrabScreenPos(clipPos)专门用于 GrabPass 纹理采样,确保 UV 合适 GrabPass 输出格式。✅ 适合 GrabPass✅ 包含垂直翻转与 API 差异处理

xxxx

总之,想要grabpass使用折射

要先转换成screen pos

采样的时候要/screenpos的w

屏幕空间像素坐标(Screen Space Pixels)

  • 这是最直观的概念:例如 Input.mousePositionCamera.ScreenToWorldPoint() 使用的坐标。

  • x ∈ [0, ⎯宽度⎯],y ∈ [0, ⎯高度⎯],左下或左上角为 (0,0)(视平台而定)Unity Discussions+7Unity Documentation+7Reddit+7Ronja Tutorials+3YouTube+3Unity Discussions+3。

  • 这种坐标就是 屏幕上实际像素位置,可直接对应 UI、鼠标位置等。

2. Shader 中 ComputeScreenPosComputeGrabScreenPos 输出的屏幕坐标

这是属于 图形管线内部的屏幕空间,它的 x, y, z, w 表示方法如下:

ComputeScreenPos(clipPos)
  • 输入是 裁剪空间clipPos

  • 输出 .xy 是齐次空间的屏幕坐标,需透视除以 .w[0,1]标准化 UV 坐标(对应整个屏幕区域)Unity Documentation+1Unity Documentation+1;

  • .xy/w 后的范围才是归一化 UV,不再是像素值。

ComputeGrabScreenPos(clipPos)
  • 功能类似,但专用于 GrabPass 后采样;

  • 处理了不同平台纹理 Y 轴的翻转和差异,确保 GrabPass 纹理正确采样️Unity Discussions+9Unity Documentation+9Unity Documentation+9;

  • 输出也需要在 fragment shader 做 /w 得出 UV。

屏幕坐标系,z依然代表深度值(继承于ndc),只是xy被拉长到1920x1080

z —— 深度值

  • 继承自 NDC(Normalized Device Coordinates),表示顶点或片元的深度信息。

  • 这个值通常经过非线性压缩:在 D3D 中范围是 [0,1],而 OpenGL 是 [-1, +1],经过统一处理后在 URP 中被转换成 [0,1] 的深度缓冲格式。stackoverflow.comdocs.unity3d.com+6docs.unity3d.com+6docs.unity3d.com+6

  • z 可用于世界坐标重构、深度测试、屏幕空间效果等,不是屏幕平面的 z = 1

fixed3 refrCol = tex2D(_RefractionTex, i.scrPos.xy / i.scrPos.w).rgb;

这里如果按照一般的屏幕空间,i.scrPos.xy / i.scrPos.w是1920x1080的大坐标,不会在0到1之间采样,

所以说o.scrPos = ComputeGrabScreenPos(o.pos);里的屏幕空间只能算是修改压缩到0到1范围里的方便采样的“屏幕空间”

靠,服了,遭罪的公式,名字起的这么常见的,里面做了什么又不教你,就是硬套是吧,

grabpass硬套,rendertexture还好多了,至少可以当一张正常的图来用

	private Texture2D _GenerateProceduralTexture() {Texture2D proceduralTexture = new Texture2D(textureWidth, textureWidth);// The interval between circlesfloat circleInterval = textureWidth / 4.0f;// The radius of circlesfloat radius = textureWidth / 10.0f;// The blur factorfloat edgeBlur = 1.0f / blurFactor;for (int w = 0; w < textureWidth; w++) {for (int h = 0; h < textureWidth; h++) {// Initalize the pixel with background colorColor pixel = backgroundColor;// Draw nine circles one by onefor (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {// Compute the center of current circleVector2 circleCenter = new Vector2(circleInterval * (i + 1), circleInterval * (j + 1));// Compute the distance between the pixel and the centerfloat dist = Vector2.Distance(new Vector2(w, h), circleCenter) - radius;// Blur the edge of the circleColor color = _MixColor(circleColor, new Color(pixel.r, pixel.g, pixel.b, 0.0f), Mathf.SmoothStep(0f, 1.0f, dist * edgeBlur));// Mix the current color with the previous colorpixel = _MixColor(pixel, color, color.a);}}proceduralTexture.SetPixel(w, h, pixel);}}proceduralTexture.Apply();return proceduralTexture;}

关键步骤说明:

1. 初始化纹理
Texture2D proceduralTexture = new Texture2D(textureWidth, textureWidth);

生成一个新的空纹理。

2. 准备参数
float circleInterval = textureWidth / 4.0f;
float radius = textureWidth / 10.0f;
float edgeBlur = 1.0f / blurFactor;
  • 圆的中心分布在 (1/4, 1/2, 3/4) 区域(因为是 (i+1)/4

  • 每个圆的半径是纹理宽度的 1/10。

  • edgeBlur 控制模糊的程度。

3. 遍历每一个像素 (w, h)
for (int w = 0; w < textureWidth; w++) {for (int h = 0; h < textureWidth; h++) {Color pixel = backgroundColor;

为每个像素准备默认色。

4. 遍历 3×3 的圆形位置(九宫格)
for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {Vector2 circleCenter = new Vector2(circleInterval * (i + 1), circleInterval * (j + 1));float dist = Vector2.Distance(new Vector2(w, h), circleCenter) - radius;

计算每个圆心到当前像素的距离 dist,减去 radius 以判断像素是“在圆内”还是“在边缘”。

5. 进行边缘模糊与颜色混合
Color color = _MixColor(circleColor, new Color(pixel.r, pixel.g, pixel.b, 0.0f), Mathf.SmoothStep(0f, 1.0f, dist * edgeBlur));
pixel = _MixColor(pixel, color, color.a);
  • Mathf.SmoothStep 产生平滑插值控制系数(类似高斯边缘)

  • _MixColor 是作者自定义的线性插值函数,通常是:

    Color _MixColor(Color a, Color b, float t) {return a * (1 - t) + b * t;
    }
    
  • 第二个参数的 alpha 是 0,表示只淡入圆的颜色边缘,不生硬覆盖背景。

6. 写入纹理像素
proceduralTexture.SetPixel(w, h, pixel);

✅ 最后一步

proceduralTexture.Apply();

这一行是必须的,它会真正把像素数据上传到 GPU

霜狼_may视频专辑-霜狼_may视频合集-哔哩哔哩视频

xxx

使用程序材质,之所以叫做程序材质,是因为里面用到的纹理是程序生成的纹理(substance designer就是)

程序材质和程序纹理都是sd里生成的

sqad的顶点结构,在模型空间下是竖直排列的

挂在摄像机上面,控制后处理

OnRenderImage(RenderTexture src, RenderTexture dest)不是 URP 或 SRP 专属的函数,它实际上是 Unity **内置渲染管线(Built-in Render Pipeline)**中的一项特有功能。

在 URP / HDRP 中是不能用 OnRenderImage 的!

原因:

SRP(Scriptable Render Pipeline)系统重写了整个渲染流程不再调用 OnRenderImage
Unity 官方在切换到 URP/HDRP 时明确废弃这类“管线钩子”方式,而转向更加结构化的 Render Feature / Pass injection 机制。

struct v2f {
    float4 pos : SV_POSITION;
    half2 uv[9] : TEXCOORD0;
};
确实是合法的 HLSL 写法,表示你想把 uv[0] ~ uv[8] 作为 9 个 half2 数据输出到片元着色器。但它是否能“正常运行”取决于 Shader Model(SM)版本 以及你绑定它的目标渲染管线(Built-in / URP / HDRP)和平台(PC / 移动)。

Sobel 卷积计算中,需要 3×3 的纹理采样坐标偏移(UV offset)
我们是否可以将这些偏移 UV 全部提前在 vertex shader 中计算好并插值传入 fragment shader,而不在 fragment 中实时计算?


✅ 通常答案是:“可以,而且不会影响结果”

这是因为:

🧠 原因:纹理 UV 插值是线性的,纹理采样坐标偏移也在 UV 空间中是线性

  • 顶点 shader 里计算的每个偏移 UV 是:

    uv + offset[i] * texelSize

  • 然后它们通过 varying 插值进入 fragment shader,UV 插值是线性的

  • 最终采样:

    tex2D(_MainTex, uv_offset[i])

由于卷积核采样点的坐标本来就是规则排列的(固定步长),所以在 fragment shader 插值得到的 uv_offset[i] 正好是你该采样的位置。

因此:

即使你没在每个 fragment 精确计算采样点偏移,而是通过插值从四个顶点计算过来的值,在大多数情况下它和 fragment 中现算的结果一致。

			fixed luminance(fixed4 color) {return  0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b; }half Sobel(v2f i) {const half Gx[9] = {-1,  0,  1,-2,  0,  2,-1,  0,  1};const half Gy[9] = {-1, -2, -1,0,  0,  0,1,  2,  1};		half texColor;half edgeX = 0;half edgeY = 0;for (int it = 0; it < 9; it++) {texColor = luminance(tex2D(_MainTex, i.uv[it]));edgeX += texColor * Gx[it];edgeY += texColor * Gy[it];}half edge = 1 - abs(edgeX) - abs(edgeY);return edge;}

它从输入结构体 i 中取出 9 个预先计算好的纹理坐标 i.uv[0~8](分别对应 3x3 的卷积核中心和其八个邻居),并对这些位置执行 Sobel 算法来检测“边缘强度”。

fixed luminance(fixed4 color) {
    return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
}
这段使用加权平均的方式计算 感知亮度,符合人眼对不同颜色敏感度(绿色最敏感),用于后续 Sobel 处理时不必再处理 RGB,而是用灰度强度。

half texColor;
half edgeX = 0;
half edgeY = 0;

for (int it = 0; it < 9; it++) {
    texColor = luminance(tex2D(_MainTex, i.uv[it]));
    edgeX += texColor * Gx[it];
    edgeY += texColor * Gy[it];
}
这里对 3×3 的采样区域进行遍历,对应位置乘上 Gx/Gy 权重,累加为边缘梯度。

half edge = 1 - abs(edgeX) - abs(edgeY);
计算的是简单的 梯度强度和(L1范数)。

再用 1 - ... 得到“非边缘强度”,所以边缘越强 → 值越小(接近 0),边缘越弱 → 值越接近 1。

你可以把这个值当作亮度返回,绘出边缘线。

“二维高斯核怎么能变成两个一维高斯核?为什么高斯模糊的结果还能一样?”

高斯核的可分性(Separable Property)

核心结论:

二维高斯核是可分离的,即一个二维的高斯函数可以拆解为两个一维的高斯函数的乘积!

using UnityEngine;
using System.Collections;[ExecuteInEditMode]
[RequireComponent (typeof(Camera))]
public class PostEffectsBase : MonoBehaviour {// Called when startprotected void CheckResources() {bool isSupported = CheckSupport();if (isSupported == false) {NotSupported();}}// Called in CheckResources to check support on this platformprotected bool CheckSupport() {if (SystemInfo.supportsImageEffects == false || SystemInfo.supportsRenderTextures == false) {Debug.LogWarning("This platform does not support image effects or render textures.");return false;}return true;}// Called when the platform doesn't support this effectprotected void NotSupported() {enabled = false;}protected void Start() {CheckResources();}// Called when need to create the material used by this effectprotected Material CheckShaderAndCreateMaterial(Shader shader, Material material) {if (shader == null) {return null;}if (shader.isSupported && material && material.shader == shader)return material;if (!shader.isSupported) {return null;}else {material = new Material(shader);material.hideFlags = HideFlags.DontSave;if (material)return material;else return null;}}
}

只是基类声明,把同用的后处理逻辑都写一起了,实际上对于图像的处理和这些没有关系

d都是子类里,onrenderimage函数,对图像进行处理,在子脚本里创建Material,接受shader用到Material上面,以及这里的函数,截取不透明||中间处理||半透明,然后利用上材质对这里进行处理,

	void OnRenderImage (RenderTexture src, RenderTexture dest) {if (material != null) {int rtW = src.width/downSample;int rtH = src.height/downSample;RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);buffer0.filterMode = FilterMode.Bilinear;Graphics.Blit(src, buffer0);for (int i = 0; i < iterations; i++) {material.SetFloat("_BlurSize", 1.0f + i * blurSpread);RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);// Render the vertical passGraphics.Blit(buffer0, buffer1, material, 0);RenderTexture.ReleaseTemporary(buffer0);buffer0 = buffer1;buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);// Render the horizontal passGraphics.Blit(buffer0, buffer1, material, 1);RenderTexture.ReleaseTemporary(buffer0);buffer0 = buffer1;}Graphics.Blit(buffer0, dest);RenderTexture.ReleaseTemporary(buffer0);} else {Graphics.Blit(src, dest);}}

OnRenderImage(RenderTexture src, RenderTexture dest) 的调用时机:

是在当前摄像机完成整个一帧的所有渲染(包括不透明、透明、天空盒等所有 passes)之后,马上被调用,用于执行“整屏后处理效果”。

所以 不只是“不透明 pass”之后,而是:

阶段是否已完成
不透明物体✅ 是
天空盒✅ 是
半透明/透明物体✅ 是
所有图像绘制完成✅ 是
OnRenderImage 调用🔜 接下来

Unity 5(估计都这样) 中 OnRenderImage()引擎级别在摄像机渲染结束后触发的回调:

  • 它不在 C# 控制的 Render Loop 中;

  • 它不是 CommandBuffer;

  • 它没有参数允许你选择“挂载在哪一阶段”。

想“完全控制渲染顺序”的方式:

唯一真正可以“自由插入任意阶段”的机制是:

  • 使用 SRP(Scriptable Render Pipeline)

  • 比如 URP / HDRP 自定义 RenderFeatureRenderPass

但 Unity 5 并不支持 SRP(那是 Unity 2018.1+ 才引入的)。

	void OnRenderImage(RenderTexture src, RenderTexture dest) {if (material != null) {int rtW = src.width;int rtH = src.height;RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);// Render the vertical passGraphics.Blit(src, buffer, material, 0);// Render the horizontal passGraphics.Blit(buffer, dest, material, 1);RenderTexture.ReleaseTemporary(buffer);} else {Graphics.Blit(src, dest);}} 

典型的 双 pass 高斯模糊(Gaussian Blur) 实现,使用 分离的垂直(vertical)和水平(horizontal)模糊 Pass

一个 shader 的两个 Pass(第 0 个和第 1 个),对图像先进行垂直方向的模糊,再进行水平方向的模糊,实现高性能、高质量的模糊效果。

void OnRenderImage(RenderTexture src, RenderTexture dest)
这个函数在一帧渲染完后被 Unity 自动调用,src 是当前帧图像,dest 是输出目标(最终屏幕或后处理链的下一个 RenderTexture)。

RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
创建一个临时 RT(buffer)用于中间结果的存储,尺寸与屏幕一致。

Graphics.Blit(src, buffer, material, 0);
将源图像 src 通过 material 的 第 0 个 Pass 处理,结果写入 buffer

这个 Pass 通常是对 Y 方向(垂直方向) 的高斯模糊

Graphics.Blit(buffer, dest, material, 1);
将上一步结果 buffer 再通过 material 的 第 1 个 Pass 处理,结果写入 dest

这个 Pass 对 X 方向(水平方向) 进行模糊

RenderTexture.ReleaseTemporary(buffer);
释放 buffer,避免内存泄漏。

else {
    Graphics.Blit(src, dest);
}
如果没有指定 material,则直接把原图 src 输出到 dest,不会有任何后处理。

为什么要用“两次一维模糊”?

高斯核的可分离性:

buffer.filterMode = FilterMode.Bilinear;
是设置这个临时 RenderTexture(buffer)的 采样过滤模式。这是非常关键的优化手段,尤其在**图像缩小(DownSample)**的后处理流程中。

✅ 它的作用是:
让 在模糊 Pass 中采样这个缩小图像时更加平滑,避免锯齿或块状感。
即:采样过程中采用 双线性插值(Bilinear Interpolation),而不是最近点采样(Point)。

模式含义用途场景(常见)
Point最近点采样(Nearest Neighbor)像素风格、无模糊
Bilinear双线性插值(插值周围 4 像素)图像缩放、模糊处理
Trilinear三线性插值(含 Mipmap 层级)3D 模型贴图、含 Mipmap 的情况

在这个模糊操作中:

你把 src 从 1920x1080 缩放到比如 480x270,再进行模糊处理。

如果你使用:

  • Point:会导致缩小时像素直接跳变,出现锯齿;

  • Bilinear:会在采样时对周围像素做插值,让模糊更自然。

CGINCLUDE 抽象出通用函数/结构,两个 Pass 共享

_BlurSize: 模糊强度倍率,控制采样偏移距离

struct v2f {
    float4 pos : SV_POSITION;
    half2 uv[5] : TEXCOORD0;
};
一次采样 5 个纹理坐标:

uv[0]: 中心

uv[1~4]: 上下或左右两个方向的 ±1、±2 像素偏移

Pass 流程(在 C# 中调用):

  1. Pass 1 垂直方向模糊
    Graphics.Blit(src, buffer, material, 0);

  2. Pass 2 水平方向模糊
    Graphics.Blit(buffer, dest, material, 1);

通过两次一维模糊,效果等价于一次二维高斯核卷积,但性能提升巨大(从 O(n²) 降为 O(2n))。

使用场景

  • 模糊背景(UI、高光)

  • 模拟散焦/景深

  • 后处理 bloom(结合亮度提取)

为什么只用了 5 个采样点?

因为每次只在 一个方向上进行卷积

  • 1 个中心点 + 上下或左右方向上各 2 个点(±1、±2 像素偏移)

  • 共 5 个点:[uv0, uv±1, uv±2]

这是对高斯分布进行的近似采样,采样权重对应的是:

weight[0] = 0.4026 // center

weight[1] = 0.2442 // ±1

weight[2] = 0.0545 // ±2

合起来能很好地逼近二维高斯核:

Pass 1:纵向模糊(中心 ±1y ±2y)

Pass 2:横向模糊(中心 ±1x ±2x)

先竖直后水平,是这样的

如果是5x5,那也是沿着这一条直线再上下再加一格

如果只是十字,那会漏了边角,这里的技巧就是遍历的时候,自己的边角会被别的像素算上

其他的模糊结果然后参与自己的模糊计算,所以最后 的表现结果就会是正确的

http://www.lryc.cn/news/601988.html

相关文章:

  • 【LeetCode 热题 100】51. N 皇后——回溯
  • c语言结构体字节对齐
  • EPOLLONESHOT 深度解析:Linux epoll 的单次触发机制
  • LeetCode 1577.数的平方等于两数乘积的方法数
  • 详解力扣高频SQL50题之180. 连续出现的数字【困难】
  • Spring MVC设计精粹:源码级架构解析与实践指南
  • 网络基础19:OSPF多区域实验
  • 俄罗斯方块游戏开发(面向对象编程)
  • Python-初学openCV——图像预处理(四)——滤波器
  • Redis6.0+安装教程(Linux)
  • vscode找不到python解释器的解决方案
  • VINS外参精确自标定飘的问题
  • Triton编译
  • C++ 多线程 std::thread::joinable
  • 3.Linuxvim编辑器及快捷键的使用
  • 【奔跑吧!Linux 内核(第二版)】第4章:内核编译和调试
  • 进度条制作--Linux知识的小应用
  • Linux 基础命令大全
  • Petalinux生成文件的关系
  • 基于Java的KTV点歌系统的设计与实现
  • 8.c语言指针
  • LeetCode 76:最小覆盖子串
  • 区块链共识机制与联邦学习
  • ArKTS:List 数组
  • D3.js的力导向图使用入门笔记
  • Java面试实战:从基础到架构的全方位技术交锋
  • 系统整理Python的条件语句和常用方法
  • PostgreSQL并发控制
  • Android Framework知识点
  • 1439-素数环2