Games101学习 - 着色

本文主要讲述Games101中的着色部分。

文中将使用UE的UTexture2D接口,若不了解可以看这篇:
https://blog.csdn.net/grayrail/article/details/142165442

1.面积比计算三角形坐标

在这里插入图片描述
通过三角形面积比可以得到三角形的坐标alpha、beta、gamma从而进行插值,或是进行图像纹理绘制,基于上一篇学习文章的三角形绘制脚本:
https://blog.csdn.net/grayrail/article/details/142211284

增加面积比插值后脚本如下:

#include "MyBlueprintFunctionLibrary.h"

float TriangleArea(const FVector2D& A, const FVector2D& B, const FVector2D& C)
{
    // 计算向量AB和AC的叉乘
    float CrossProduct = (B.X - A.X) * (C.Y - A.Y) - (B.Y - A.Y) * (C.X - A.X);

    // 返回三角形面积(取叉乘结果的绝对值的一半)
    return FMath::Abs(CrossProduct) * 0.5f;
}

// 计算P点的重心坐标
FVector GetBarycentricCoordinates(const FVector2D& P, const FVector2D& A, const FVector2D& B, const FVector2D& C)
{
    // 计算ABC的总面积
    float AreaABC = TriangleArea(A, B, C);

    // 计算PBC, PCA, PAB的面积
    float AreaPBC = TriangleArea(P, B, C);
    float AreaPCA = TriangleArea(P, C, A);
    float AreaPAB = TriangleArea(P, A, B);

    // 重心坐标
    float alpha = AreaPBC / AreaABC;
    float beta = AreaPCA / AreaABC;
    float gamma = AreaPAB / AreaABC;

    return FVector(alpha, beta, gamma); // 返回重心坐标(alpha, beta, gamma)
}

UTexture2D* UMyBlueprintFunctionLibrary::GenTexture(int32 Width, int32 Height)
{
    // 创建临时纹理
    UTexture2D* NewTexture = UTexture2D::CreateTransient(Width, Height);

    // 配置纹理
    NewTexture->MipGenSettings = TMGS_NoMipmaps;
    NewTexture->CompressionSettings = TC_VectorDisplacementmap;
    NewTexture->SRGB = false;

    // 锁定纹理数据进行写入
    FTexture2DMipMap& Mip = NewTexture->PlatformData->Mips[0];
    void* TextureData = Mip.BulkData.Lock(LOCK_READ_WRITE);

    // 设置默认颜色为黑色
    FColor* FormattedImageData = static_cast<FColor*>(TextureData);
    for (int32 y = 0; y < Height; ++y)
    {
        for (int32 x = 0; x < Width; ++x)
        {
            FormattedImageData[y * Width + x] = FColor::Black; // 背景颜色设置为黑色
        }
    }

    // 定义三角形顶点(A, B, C)
    FVector2D A(Width / 2, Height / 4);  // 三角形顶点A
    FVector2D B(Width / 4, 3 * Height / 4); // 三角形顶点B
    FVector2D C(3 * Width / 4, 3 * Height / 4); // 三角形顶点C

    // 深红色
    FColor TriangleColor = FColor(139, 0, 0, 255);

    // 叉乘判断点P是否在三角形ABC内
    auto IsPointInTriangle = [](const FVector2D& P, const FVector2D& A, const FVector2D& B, const FVector2D& C) -> bool
        {
            FVector2D AP = P - A;
            FVector2D BP = P - B;
            FVector2D CP = P - C;

            FVector2D AB = B - A;
            FVector2D BC = C - B;
            FVector2D CA = A - C;

            // 叉乘结果
            float Cross1 = AB.X * AP.Y - AB.Y * AP.X; // AB 和 AP 的叉乘
            float Cross2 = BC.X * BP.Y - BC.Y * BP.X; // BC 和 BP 的叉乘
            float Cross3 = CA.X * CP.Y - CA.Y * CP.X; // CA 和 CP 的叉乘

            // 如果三个叉乘结果符号相同,则点在三角形内
            return (Cross1 >= 0 && Cross2 >= 0 && Cross3 >= 0) || (Cross1 <= 0 && Cross2 <= 0 && Cross3 <= 0);
        };

    int SubPixelCount = 8;

    // 超采样抗锯齿:子像素划分
    float SubPixelStep = 1.0f / SubPixelCount; // 子像素的步长
    int32 TotalSubPixels = SubPixelCount * SubPixelCount; // 子像素的总数

    // 遍历每个像素并应用抗锯齿逻辑
    for (int32 y = 0; y < Height; ++y)
    {
        for (int32 x = 0; x < Width; ++x)
        {
            int32 CoveredSubPixels = 0;

            // 遍历 SubPixelCount x SubPixelCount 子像素
            for (int32 subY = 0; subY < SubPixelCount; ++subY)
            {
                for (int32 subX = 0; subX < SubPixelCount; ++subX)
                {
                    FVector2D SubPixelPos = FVector2D(x + (subX + 0.5f) * SubPixelStep, y + (subY + 0.5f) * SubPixelStep); // 子像素位置
                    if (IsPointInTriangle(SubPixelPos, A, B, C))
                    {
                        CoveredSubPixels++;
                    }
                }
            }

            // 计算覆盖率并设置像素颜色
            float Coverage = static_cast<float>(CoveredSubPixels) / TotalSubPixels; // 覆盖率(0 到 1)
            if (Coverage > 0)
            {
                FVector2D P(x, y);
                FVector BaryCoords = GetBarycentricCoordinates(P, A, B, C);
                BaryCoords.X = FMath::RoundToInt(BaryCoords.X * 255 * Coverage);
                BaryCoords.Y = FMath::RoundToInt(BaryCoords.Y * 255 * Coverage);
                BaryCoords.Z = FMath::RoundToInt(BaryCoords.Z * 255 * Coverage);

                FColor FinalColor = FColor(BaryCoords.X, BaryCoords.Y, BaryCoords.Z);
                FormattedImageData[y * Width + x] = FinalColor;
            }
        }
    }

    // 解锁纹理数据
    Mip.BulkData.Unlock();
    NewTexture->UpdateResource();

    return NewTexture;
}

绘制结果如下:
在这里插入图片描述

此外,gamma插值信息的获取,也可以这样修改:

// 重心坐标
float alpha = AreaPBC / AreaABC;
float beta = AreaPCA / AreaABC;
float gamma = 1 - alpha - beta;

2.双线性插值 Bilinear

因为实际屏幕采样像素时需要考虑到材质大小、屏幕占比等因素,采样图片并不会像CPU采样那样都是整数,而这种0-1浮点数的坐标采样会带来走样问题,因此通过Bilinear双线性采样的方式采样周围4个像素并进行插值,从而得到更好的采样结果:
在这里插入图片描述
代码如下:

UTexture2D* UMyBlueprintFunctionLibrary::GenTexture(int32 Width, int32 Height, UTexture2D* SourceTexture)
{
    if (!SourceTexture)
    {
        UE_LOG(LogTemp, Error, TEXT("SourceTexture is null"));
        return nullptr;
    }

    // 创建一个新的UTexture2D
    UTexture2D* NewTexture = UTexture2D::CreateTransient(Width, Height, SourceTexture->GetPixelFormat());
    if (!NewTexture)
    {
        UE_LOG(LogTemp, Error, TEXT("Failed to create new texture"));
        return nullptr;
    }

    // 锁定源纹理和新纹理的内存
    FTexture2DMipMap& SourceMip = SourceTexture->PlatformData->Mips[0];
    FTexture2DMipMap& DestMip = NewTexture->PlatformData->Mips[0];

    // 获取源纹理的像素数据
    uint8* SourcePixels = static_cast<uint8*>(SourceMip.BulkData.Lock(LOCK_READ_ONLY));
    uint8* DestPixels = static_cast<uint8*>(DestMip.BulkData.Lock(LOCK_READ_WRITE));

    int32 SourceWidth = SourceMip.SizeX;
    int32 SourceHeight = SourceMip.SizeY;

    int32 PixelSize = 4; // 假设使用的是标准的8位RGBA纹理

    // 进行采样并将数据写入到新纹理中
    for (int32 y = 0; y < Height; ++y)
    {
        for (int32 x = 0; x < Width; ++x)
        {
            // 计算源纹理中的浮动坐标
            float U = static_cast<float>(x) / Width * (SourceWidth - 1);
            float V = static_cast<float>(y) / Height * (SourceHeight - 1);

            // 获取四个邻近像素的坐标
            int32 X0 = static_cast<int32>(FMath::FloorToInt(U));
            int32 X1 = FMath::Clamp(X0 + 1, 0, SourceWidth - 1);
            int32 Y0 = static_cast<int32>(FMath::FloorToInt(V));
            int32 Y1 = FMath::Clamp(Y0 + 1, 0, SourceHeight - 1);

            // 获取插值因子
            float FracX = U - X0;
            float FracY = V - Y0;

            // 计算四个顶点的索引
            int32 Index00 = (Y0 * SourceWidth + X0) * PixelSize;
            int32 Index01 = (Y1 * SourceWidth + X0) * PixelSize;
            int32 Index10 = (Y0 * SourceWidth + X1) * PixelSize;
            int32 Index11 = (Y1 * SourceWidth + X1) * PixelSize;

            // 线性插值四个像素点
            auto Lerp = [](uint8 A, uint8 B, float T) -> uint8 {
                return FMath::Clamp(static_cast<int32>(FMath::Lerp(static_cast<float>(A), static_cast<float>(B), T)), 0, 255);
                };

            // 对R、G、B、A分别进行插值
            uint8 R = Lerp(Lerp(SourcePixels[Index00], SourcePixels[Index10], FracX),
                Lerp(SourcePixels[Index01], SourcePixels[Index11], FracX),
                FracY);
            uint8 G = Lerp(Lerp(SourcePixels[Index00 + 1], SourcePixels[Index10 + 1], FracX),
                Lerp(SourcePixels[Index01 + 1], SourcePixels[Index11 + 1], FracX),
                FracY);
            uint8 B = Lerp(Lerp(SourcePixels[Index00 + 2], SourcePixels[Index10 + 2], FracX),
                Lerp(SourcePixels[Index01 + 2], SourcePixels[Index11 + 2], FracX),
                FracY);
            uint8 A = Lerp(Lerp(SourcePixels[Index00 + 3], SourcePixels[Index10 + 3], FracX),
                Lerp(SourcePixels[Index01 + 3], SourcePixels[Index11 + 3], FracX),
                FracY);

            // 写入目标纹理
            int32 DestIndex = (y * Width + x) * PixelSize;
            DestPixels[DestIndex] = R;
            DestPixels[DestIndex + 1] = G;
            DestPixels[DestIndex + 2] = B;
            DestPixels[DestIndex + 3] = A;
        }
    }

    // 解锁像素数据
    SourceMip.BulkData.Unlock();
    DestMip.BulkData.Unlock();

    // 更新新纹理
    NewTexture->UpdateResource();

    return NewTexture;
}

注意需要更新蓝图节点:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/881664.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ChatGPT 4o 使用指南 (9月更新)

首先基础知识还是要介绍得~ 一、模型知识&#xff1a; GPT-4o&#xff1a;最新的版本模型&#xff0c;支持视觉等多模态&#xff0c;OpenAI 文档中已经更新了 GPT-4o 的介绍&#xff1a;128k 上下文&#xff0c;训练截止 2023 年 10 月&#xff08;作为对比&#xff0c;GPT-4…

【Linux笔记】虚拟机内Linux内容复制到宿主机的Window文件夹(文件)中

一、共享文件夹 I、Windows宿主机上创建一个文件夹 目录&#xff1a;D:\Centos_iso\shared_files II、在VMware中设置共享文件夹 1、打开VMware Workstation 2、选择需要设置的Linux虚拟机&#xff0c;点击“编辑虚拟机设置”。 3、在“选项”标签页中&#xff0c;选择“共…

Vue学习记录之三(ref全家桶)

ref、reactive是在 setup() 声明组件内部状态用的&#xff0c; 这些变量通常都要 return 出去&#xff0c;除了供 < template > 或渲染函数渲染视图&#xff0c;也可以作为 props 或 emit 参数 在组件间传递。它们的值变更可触发页面渲染。 ref &#xff1a;是一个函数&…

前端组件库

vant2现在的地址 Vant 2 - Mobile UI Components built on Vue

【学习笔记】手写Tomcat 四

目录 一、Read 方法返回 -1 的问题 二、JDBC 优化 1. 创建配置文件 2. 创建工具类 3. 简化 JDBC 的步骤 三、修改密码 优化返回数据 创建修改密码的页面 注意 测试 四、优化响应动态资源 1. 创建 LoginServlet 类 2. 把登录功能的代码放到 LoginServlet 类 3. 创…

【工具变量】科技金融试点城市DID数据集(2000-2023年)

时间跨度&#xff1a;2000-2023年数据范围&#xff1a;286个地级市包含指标&#xff1a; year city treat post DID&#xff08;treat*post&#xff09; 样例数据&#xff1a; 包含内容&#xff1a; 全部内容下载链接&#xff1a; 参考文献-pdf格式&#xff1a;https://…

Rust GUI框架 tauri V2 项目创建

文章目录 Tauri 2.0创建应用文档移动应用开发 Android 前置要求移动应用开发 iOS 前置要求参考资料 Tauri 2.0 Tauri 是一个构建适用于所有主流桌面和移动平台的轻快二进制文件的框架。开发者们可以集成任何用于创建用户界面的可以被编译成 HTML、JavaScript 和 CSS 的前端框架…

使用madExcept检测内存泄漏

代码异常堆栈跟踪&#xff1a;Mad Except 一、安装 官网 运行&#xff0c;选择madExcept5然后安装。 输入yes继续 二、使用 新建一个VCL项目 在project中多了一项设置 选择OK后会发现项目多了几个引用单元。 此时运行程序&#xff0c;再退出&#xff0c;会显示没有任何内存…

嵌入式入门小工程

此代码基于s3c2440 1.点灯 //led.c void init_led(void) {unsigned int t;t GPBCON;t & ~((3 << 10) | (3 << 12) | (3 << 14) | (3 << 16));t | (1 << 10) | (1 << 12) | (1 << 14) | (1 << 16);GPBCON t; }void le…

道路裂缝,坑洼,病害数据集-包括无人机视角,摩托车视角,车辆视角覆盖道路

道路裂缝&#xff0c;坑洼&#xff0c;病害数据集 包括无人机视角&#xff0c;摩托车视角&#xff0c;车辆视角 覆盖道路所有问题 一共有八类16000张 1到7依次为: [横向裂缝, 纵向裂缝, 块状裂缝, 龟裂, 坑槽, 修补网状裂缝, 修补裂缝, 修补坑槽] 道路病害&#xff08;如裂缝、…

CTC loss 博客转载

论文地址&#xff1a; https://www.cs.toronto.edu/~graves/icml_2006.pdf 为了对应这个图&#xff0c;我们假设一种符合的模型情况&#xff1a; 英文OCR&#xff0c;37个类别&#xff08;26个小写字母10个汉字空格&#xff09;&#xff0c;最大输出长度8个字符 模型预测结果…

使用 nvm 管理 node 版本:如何在 macOS 和 Windows 上安装使用nvm

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、引言二、nvm的安装与基本使用2.1 macOS安装nvm2.1.1 使用 curl 安装2.1.2 使用 Homebrew 安装 2.2 Windows安装nvm2.2.1 下载 nvm-windows2.2.2 安装 nvm-windows 2.3 安装node2.4 切换node版本 三、常见问题及解决方案…

前端JavaScript导出excel,并用excel分析数据,使用SheetJS导出excel

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f…

【C高级】有关shell脚本的一些练习

目录 1、写一个shell脚本&#xff0c;将以下内容放到脚本中&#xff1a; 2、写一个脚本&#xff0c;包含以下内容&#xff1a; 1、写一个shell脚本&#xff0c;将以下内容放到脚本中&#xff1a; 1、在家目录下创建目录文件&#xff0c;dir 2、dir下创建dir1和dir2 …

电商ISV 电商SaaS 是什么

Independent Software Vendors的英文缩写&#xff0c;意为“独立软件开发商” 软件即服务(SaaS) 指一种基于云技术的软件交付模式 订阅收费 这些公司叫做ISV软件供应商&#xff0c;通过SaaS服务交付收费 为什么会有电商ISV 从商家角度划分&#xff1a;有独立品牌商家、大商…

【2025】儿童疫苗接种预约小程序(源码+文档+解答)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

猫咪检测系统源码分享

猫咪检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vision …

R语言机器学习算法实战系列(二) SVM算法(Support Vector Machine)

文章目录 介绍原理应用方向下载数据加载R包导入数据数据预处理数据描述数据切割标准化数据设置参数训练模型预测测试数据评估模型模型准确性混淆矩阵模型评估指标ROC CurvePRC Curve特征的重要性保存模型总结系统信息介绍 支持向量机(Support Vector Machine,简称SVM)是一种…

跨站请求伪造(CSRF)漏洞详解

免责申明 本文仅是用于学习检测自己搭建的DVWA靶场环境有关CSRF的原理和攻击实验,请勿用在非法途径上,若将其用于非法目的,所造成的一切后果由您自行承担,产生的一切风险和后果与笔者无关;本文开始前请认真详细学习《‌中华人民共和国网络安全法》‌及其所在国家地区相关法…

java项目之在线考试与学习交流网页平台源码(springboot)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的在线考试与学习交流网页平台。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 基于JAVA语言…