Games101学习 - 着色

news2024/9/20 11:48:30

本文主要讲述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;
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MATLAB画图,曲线图如何绘制美观,曲线图10种美化方法

曲线图是比较常用的图形&#xff0c;本文以二维曲线图为例&#xff0c;展示曲线的图的不同美化方法&#xff0c;如图1所示&#xff0c;是一个标准的曲线图&#xff0c;横坐标为x&#xff0c;纵坐标为y, 图1 标准曲线图 调整方法1 首先可以通过改变线的颜色&#xff0c;不同…

react + antDesign封装图片预览组件(支持多张图片)

需求场景&#xff1a;最近在开发后台系统时经常遇到图片预览问题&#xff0c;如果一个一个的引用antDesign的图片预览组件就有点繁琐了&#xff0c;于是在antDesign图片预览组件的基础上二次封装了一下&#xff0c;避免重复无用代码的出现 效果 公共预览组件代码 import React…

Python安装不再难!全平台保姆级教程带你轻松搞定!

Python介绍 Python是一种功能强大且灵活的编程语言&#xff0c;被广泛应用于各个领域。以下是Python在不同应用领域的一些常见用途&#xff1a; 网络开发 Python提供了丰富的库和框架&#xff0c;使其成为网络开发的理想选择。诸如Django、Flask和Pyramid等框架可以帮助开发人员…

从 HDFS 迁移到 MinIO 企业对象存储

云原生、面向 Kubernetes 、基于微服务的架构推动了对 MinIO 等网络存储的需求。在云原生环境中&#xff0c;对象存储的优势很多 - 它允许独立于存储硬件对计算硬件进行弹性扩展。它使应用程序无状态&#xff0c;因为状态是通过网络存储的&#xff0c;并且通过降低操作复杂性&a…

Vue使用组件需要加前缀而React使用组件库的区别

Vue 写在模版中的内容最终会被render&#xff0c;render时会区分标签与组件。 通过-短横线命名法 或 大驼峰命名法使用组件 <a-button><a-button/> <MyComponent></MyComponent>但是-短横线命名法容易引起歧义&#xff0c;比如组件名是一个单词(无法…

learn C++ NO.17——继承

什么是继承&#xff1f; 用冒号 : 后跟基类名称来声明一个类是从某个基类继承而来的。继承方式可以是 public、protected 或 private&#xff0c;这决定了基类成员在子类中的访问权限。 下面通过代码简单进行一下演示. 派生类Student即子类&#xff0c;而基类Person是它的父…

浏览器恢复历史记录应该怎么操作?简单几步轻松搞定

浏览器的历史记录是用户上网过程中产生的所有浏览活动的记录。这些历史记录对于查找之前访问过的网站、恢复误关闭的页面&#xff0c;以及跟踪浏览活动有很大的帮助。当然有时候我们可能会不小心将浏览器历史记录给删除了&#xff0c;那浏览器清除的历史记录可以恢复吗&#xf…

Linux 信号的产生

1. 概念 在Linux系统中&#xff0c;信号是一种进程间通信的机制&#xff0c;它允许操作系统或其他进程向特定进程发送异步通知。我们可以通过命令 kill -l来查看信号的种类&#xff1a; Linux系统中的信号可以分为两大类&#xff1a;传统信号和实时信号。从上图可以看出它们分…

代码随想录算法训练营第40天 动态规划part07| 题目: 198.打家劫舍 、 213.打家劫舍II 、 337.打家劫舍III

代码随想录算法训练营第40天 动态规划part07| 题目&#xff1a; 198.打家劫舍 、 213.打家劫舍II 、37.打家劫舍III 文章来源&#xff1a;代码随想录 题目名称&#xff1a;198.打家劫舍 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff…

【随手笔记】485

1. 基础知识 2线&#xff0c;半双工&#xff0c;多点通信 电压差传递信号 逻辑 1&#xff1a; 两线间电压差为 2V ~ 6V 逻辑0 &#xff1a; 两线间电压差为-2V ~ -6V 10米最高速率达 35Mbps 1200米 速率达100Kbps 抗共模干扰能力强 一般支持32个节点 推荐使用点对点线型 总线…

IDEA开发HelloWorld程序

IDEA管理Java程序的结构 project&#xff08;项目、工程&#xff09;---project中可以创建多个modulemodule&#xff08;模块&#xff09;---module中可以创建多个packagepackage&#xff08;包&#xff09;---package中可以创建多个classclass&#xff08;类&#xff09;---c…

木牛科技PMO总监关沨受邀为第四届中国项目经理大会演讲嘉宾

全国项目经理专业人士年度盛会 北京木牛领航科技有限公司PMO总监关沨女士受邀为PMO评论主办的全国项目经理专业人士年度盛会——2024第四届中国项目经理大会演讲嘉宾&#xff0c;演讲议题为“如何培养能打胜仗的项目经理”。大会将于10月26-27日在北京举办&#xff0c;主题为&a…

图神经网络在推荐系统中的应用综述

1 研究计划 了解推荐系统的研究背景和发展历程了解为什么推荐系统需要GNN了解基于GNN的推荐的关键挑战了解基于GNN的推荐的现有方法 2 完成情况 2.1推荐系统的研究背景和发展历程 随着各种服务和平台(如电子商务、短视频等)上信息的快速爆炸&#xff0c;推荐系统在缓解信息…

UWA支持鸿蒙HarmonyOS NEXT

华为在开发者大会上&#xff0c;宣布了鸿蒙HarmonyOS NEXT将仅支持鸿蒙内核和鸿蒙系统的应用&#xff0c;不再兼容安卓应用&#xff0c;这意味着它将构建一个全新且完全独立的生态系统。 为此&#xff0c;UWA也将在最新版的UWA SDK v2.5.0中支持鸿蒙HarmonyOS NEXT&#xff0c…

NLP三天入门大模型,我领先你好几个版本了

大模型时代下&#xff0c;nlp初学者需要怎么入门? 入门姿势简单粗暴:打一些必要的基础就跑步进入Transformera 大模型时代&#xff0c;传统的算法&#xff0c;像分词、词性标注&#xff0c;被替代得非常厉害&#xff0c;在入门阶段没必要花费太多精力在传统算法上面。 数学和…

强弱电的基本知识和区别

什么是弱电&#xff1a; 弱电一般是指直流电路或音频、视频线路、网络线路、电话线路&#xff0c;直流电压一般在36V以内。家用电器中的电话、电脑、电视机的信号输入&#xff08;有线电视线路&#xff09;、音响设备&#xff08;输出端线路&#xff09;等用电器均为弱电电气设…

IDEA Cody 插件实现原理

近年来&#xff0c;智能编程助手 在开发者日常工作中变得越来越重要。IDEA Cody 插件是 JetBrains 生态中一个重要的插件&#xff0c;它可以帮助开发者 快速生成代码、自动补全、并提供智能提示&#xff0c;从而大大提升开发效率。今天我们将深入探讨 Cody 插件的实现原理&…

Facebook隐私设置指南:如何更好地保护个人信息

在数字化时代&#xff0c;隐私保护成为了每个互联网用户面临的重要课题。Facebook&#xff0c;作为全球最大的社交网络平台之一&#xff0c;拥有庞大的用户基础和丰富的个人数据。因此&#xff0c;了解和管理Facebook的隐私设置对保护个人信息至关重要。本文将为您提供一份详细…

RTX 4090/RTX 4090D停产,为RTX 5090扫平“障碍”

原文转载修改自&#xff08;更多互联网新闻/搞机小知识&#xff09;&#xff1a; RTX 4090/4090D或于10月停产&#xff0c;为RTX 5090“登基”铺路 作为网络人均一代旗舰的RTX 4090至今也已发售近两年&#xff0c;说实在的&#xff0c;按老黄一贯的手法&#xff0c;也到了该落…

金属材质检测系统源码分享

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