[游戏开发]Unity颜色矫正无障碍方案

news2024/12/21 14:09:40

[目录]

  • 0. 前言
  • 1. 颜色矫正
  • 2. 线性变换Shader
  • 2. 颜色纠正参数
  • 3. 摄像机后处理
  • 4. 效果
  • 5. 结束咯

0. 前言

之前有在关注色盲视觉纠正问题,最近在调整游戏的时候就打算把这个用上。

色弱色盲,这其实算是一种误称吧,只是人类中的少数派,只不过看到的颜色和大部分人不一样。下文用,视觉少数者,来称呼吧。

本质上是因为感知颜色的细胞发生突变,感知与大部分人有差异。之前就一直在想能不能有一些方法对颜色做一些调整作为纠正。比如说红色感知弱,显示的时候把红强度提高作为弥补。但目前来说好像还没有确切的方案来执行,甚至色盲色弱的标准以及测试都不清楚,何谈纠正呢。

主要是色觉其实是比较复杂的,人有3种感光细胞用于感知光线,然后感知之后再由大脑脑补一下出画面。这其中的过程并不能够简单映射得出结果,比如同种颜色在不通颜色的比较下会感知不同。而且三种感光细胞还有一定的感光范围,和RBG这种常用的颜色空间基准还有偏差。所以这部分是比较难处理的。另外如果从小看到的火就是蓝色的,那后面蓝色这个颜色的意义也会发生改变,这个也难以统一。

1. 颜色矫正

颜色矫正是为了让颜色更好区分,并不是开了之后就会让视觉少数者感知到其他人一样的颜色。处理还是有挺多种方法的。

  • 颜色映射,红色映射为某一种颜色,紫色映射为另一种等等,这种方法主要是映射空间难以确定,而且运行比较复杂,一般只有对图片做处理才会有这种方式。
  • 旋转H分量,在HSV空间缩放旋转H分量的方式,这样在色环上做的处理就可以让颜色均匀,只不过旋转角度难以确定、前后颜色差异比较大。
  • 线性变换,RGB颜色做一个线性变换,这样颜色可以做到均匀计算量也比较小。至于能否在线性空间内找到合适的方法,或者因人而异的指定参数这个还是比较难确定。

基本上目前软件上,基本都是用第三种做的矫正处理,目前window中就是用的这个,所以后续讲的也是如此,参数也是用window开纠正后的颜色,反向求解出来的,效果应该还可以。对于应用到游戏中、UnIty中,目前考虑的方法就是用写一个 颜色线性变换Shader,确定好不同的颜色纠正参数,然后在摄像机后处理的时候做统一的纠正处理。

2. 线性变换Shader

这里就简单的写一个RGB线性变换的shder,主要目的是让颜色与给定的矩阵做叉乘,如下。

Shader "MyShader/ColorTr" {
    Properties {
        [PerRendererData] _MainTex ("Texture", 2D) = "white" {}
        _UtR ("UtR",Vector)=(0.14,0.86,0,0)
        _UtG ("UtG",Vector)=(0.14,0.86,0,0)
        _UtB ("UtB",Vector)=(0,0,1,0)
    }
    SubShader {
        Pass {
            CGPROGRAM
            
            #include "UnityCG.cginc"
            
            #pragma vertex vert
            #pragma fragment frag

            sampler2D _MainTex;
            float4 _UtR;
            float4 _UtG;
            float4 _UtB;
            
			struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
                //float3 color : COLOR0;
            };

            struct v2f {
                float4 pos : SV_POSITION;
                float3 texcoord : COLOR0;
            };
            
            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.texcoord = v.texcoord;
                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                float4 color = tex2D(_MainTex, i.texcoord);
                color.r = mul(_UtR,color);
                color.g = mul(_UtG,color);
                color.b = mul(_UtB,color);
                return color;
            }
            
            ENDCG
        } 
    }
    FallBack Off
}

好了,那么后面我们只需要修改_UtR 等3个矩阵点的值,就可以调整颜色线性变换的结果了。

2. 颜色纠正参数

我们先定义下颜色纠正类型,常规/红色/绿色/蓝色,

public enum ColorMappingType
{
    Normal,
    Red,
    Green,
    Blue,
}

具体的参数怎么去确定呢,参考window的颜色变换,我们只要获取到对应的颜色变换对,就可以通通过矩阵求逆的方式来得到和这个参数了,如下。color为原颜色,colorF为映射得到的函数

private double[,] GetTranslateU(double[,] color, double[,] colorF)
{
    double[,] colorAthwart = MatrixF.Athwart(color);
    return MatrixF.MultiplyMatrix(colorF, colorAthwart);
}
// 矩阵运算相关内容是参考的这里
// https://blog.csdn.net/Lynn_whu/article/details/80745634

如果不用矩阵运算也可以,假设原本的颜色为(R1,G1,B1),变换后的颜色为(R2, G2, B2),线性变换矩阵为((a,b,c),(d,e,f),(g,h,i)},那么这个线性变换其实就是如下的结果。3个方程式,只要找到颜色变换对,代入就可以求出这几个参数了。(不过估计还挺难算的。)

R2 = a * R1 + b * G1 + c * B1
G2 = d * R1 + e * G1 + f * B1
B2 = g * R1 + h * G1 + i * B1

那么求逆矩然后再求解之后,可得参数如下:

// 红色
new double[3,3]{
    {1,0,0}, {0.47,0.49,0.04}, {0.59,-0.68,1.09},
},
// 绿色
new double[3,3]{
    {1.22,-0.31,0.11}, {0,1,0}, {-0.18,0.17,1},
},
// 蓝色
new double[3,3]{
    {0.74,-0.39,0.66}, {0.08,0.59,0.33}, {0,0,1},
}

3. 摄像机后处理

场景等物体渲染后再做处理,用摄像机后处理来处理就很方便了。写个脚本控制一下

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


namespace GDT
{
    [RequireComponent(typeof(Camera))]
    public class ColorMapping : MonoBehaviour
    {
        public Material materialMapping;

        private double[][,] MappingUt;
        private Material curMaterial;

        private void Awake()
        {
            MappingUt = new double[3][,]{
                // 红色
                new double[3,3]{
                    {1,0,0}, {0.47,0.49,0.04}, {0.59,-0.68,1.09},
                },
                // 绿色
                new double[3,3]{
                    {1.22,-0.31,0.11}, {0,1,0}, {-0.18,0.17,1},
                },
                // 蓝色
                new double[3,3]{
                    {0.74,-0.39,0.66}, {0.08,0.59,0.33}, {0,0,1},
                }
            };
        }

        public void SetMaping(ColorMappingType type)
        {
            switch (type)
            {
                case ColorMappingType.Normal:
                    curMaterial = null;
                    break;
                case ColorMappingType.Red:
                    ChangeShaderColorTraU(MappingUt[0]);
                    curMaterial = materialMapping;
                    break;
                case ColorMappingType.Green:
                    ChangeShaderColorTraU(MappingUt[1]);
                    curMaterial = materialMapping;
                    break;
                case ColorMappingType.Blue:
                    ChangeShaderColorTraU(MappingUt[2]);
                    curMaterial = materialMapping;
                    break;
            }
        }

        private void OnRenderImage(RenderTexture source, RenderTexture destination)
        {
            //https://blog.csdn.net/qq_31042143/article/details/127186310
            if (curMaterial)
            {
                Graphics.Blit(source, destination, curMaterial);
            }
            else
            {
                Graphics.Blit(source, destination);
            }
            // R2 = a * R1 + b * G1 + c * B1
            // G2 = d * R1 + e * G1 + f * B1
            // B2 = g * R1 + h * G1 + i * B1
        }

        private void ChangeShaderColorTraU(double[,] Ut)
        {
            //Debug.LogError("Ut:" + Ut);
            materialMapping.SetVector("_UtR", new Vector4((float)Ut[0, 0], (float)Ut[0, 1], (float)Ut[0, 2], 0));
            materialMapping.SetVector("_UtG", new Vector4((float)Ut[1, 0], (float)Ut[1, 1], (float)Ut[1, 2], 0));
            materialMapping.SetVector("_UtB", new Vector4((float)Ut[2, 0], (float)Ut[2, 1], (float)Ut[2, 2], 0));
        }
    }
}

  • [RequireComponent(typeof(Camera))]
    因为需要进行相机后处理,所以要求了一下组件类型
  • SetMaping 设置颜色纠正方式
  • OnRenderImage 进行摄像机后处理
  • ChangeShaderColorTraU 设置shader参数

然后把脚本挂到摄像机对象上就完成了。

4. 效果

在这里插入图片描述
效果还是比较明显的,下面是参照颜色条,和window 上的变换一致。

实现还是挺简单的,只需要挂在显示的摄像机上就可以了,不会影响到其他shader的处理,场景和游戏对象都不用另外做处理。

5. 结束咯

到这里就结束了。线性变换的参数还是有很大操作空间的,不仅可以用于视觉少数者纠正颜色,也可以用于做一些视觉特殊效果,比如说画面变黄等滤镜效果。只要绑定到特殊的摄像头上,或者直接用于对应物体的材质上就可以了。

希望能够提供参考组作用。

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

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

相关文章

保护您的数据与ManageEngine Log360

在当今数字时代,网络安全成为了企业和组织不可忽视的重要议题。随着信息技术的发展和互联网的普及,企业面临着越来越多的网络威胁和数据泄露的风险。为了保护重要的数据资产和防止潜在的攻击,日志管理和事件关联成为了至关重要的一环。 Mana…

IIS安装ARR(Application Request Router)负载均衡扩展

IIS7.5安装ARR(Application Request Router)负载均衡扩展 本文主要记录我在IIS中安装ARR的全流程,本文参考了网上一些教程,但可能时间关系,与一些早期文章所述有所出入。 花了我半天的时间才最终安装成功,因此这里做一个记录。本…

立体解析Fiddler Filters:让你快速捕获和过滤网络请求

如果要对当前Fiddler的抓包进行过滤(如过滤掉与测试项目无关的抓包请求),那功能强大的 Filters 过滤器能帮到你。 如果你想学习Fiddler抓包工具,我这边给你推荐一套视频,这个视频可以说是B站播放全网第一的Fiddler抓包…

动态规划算法(多状态dp1)

动态规划算法专辑之多状态dp问题(1) 一、什么是多状态 多状态dp问题,指一个规模问题下存在多种状态,我们需要联合关注多种状态间的相互转移,才可以求解目的问题。 多状态问题可以理解为有限状态机,在有限…

节省时间、提升效率——Jetpack关爱你的摸鱼时间

JetPack Jetpack 是一个由 Google 提供的 Android 应用开发库集合。它旨在简化 Android 应用程序开发过程,提供一系列的库和工具,帮助开发者快速构建高质量、健壮、可扩展的 Android 应用。 Jetpack 包含多个组件,每个组件都专注于不同的功…

Nginx 的reload,升级以及关闭流程

一、reload流程 1 向master进程发送HUP信号(reload命令) 2 master进程校验配置语法是否正确; 3 master打开可能引入的新的监听端口; 4 master用新的配置文件启动新的worker子进程; 5 启动新的worker子进程之后&#x…

小马赠书【第8期】清华社 618 IT BOOK 多得活动(送书5本)

本期 敬 之 共精心挑选了 15 本 IT 相关书籍,包含 前端、后端、数据分析、人工智能、python 等各个领域。关于如何参与等具体活动信息请看活动详情页,以下是 15 本 IT 书籍介绍: 活动详情页:小马赠书【第8期】 1. 《Linux设备驱动…

怎么入手性能测试,重点以及各项流程

之前在性能测试学习路线里,提到过《软件性能测试、分析与调优实践之路》这本书。 昨天看到之前自己记的读书笔记,整理一下发出来,希望对读者有所帮助。 网上关于性能测试的文章大多数时间比较久远,或者知识点比较散,…

南大一作!科学家发现全新量子态 | Nature速递

光子盒研究院 马萨诸塞大学助理教授Tigran Sedrakyan在内的一个物理学家团队最近在《自然》杂志上宣布,他们已经发现了一种新的物质阶段——“手性玻色液态(chiral Bose-liquid state)”,这一突破为理解物理世界本质的古老努力开辟了一条全新道路。 团队…

【目标跟踪】MOT数据集GroundTruth可视化

MOT数据集格式简介 MOT15数据集下载&#xff1a;https://pan.baidu.com/s/1foGrBXvsanW8BI4eybqfWg?pwd8888 以下为一行gt示例&#xff1a; 1,1,1367,393,73,225,1,-1,-1,-1 各列数据对应含义如下 <frame>,<id>,<bb_left>,<bb_top>,<bb_width&g…

autocut

在讲 OpenAI Whisper 前先做了一个剪视频小工具【论文精读44】_哔哩哔哩_bilibili更多论文&#xff1a;https://github.com/mli/paper-reading, 视频播放量 58633、弹幕量 233、点赞数 2732、投硬币枚数 1630、收藏人数 1465、转发人数 604, 视频作者 跟李沐学AI, 作者简介 &am…

什么是python,一篇带你详细了解Python

Python是一种跨平台的计算机程序设计语言&#xff0c;是ABC语言的替代品&#xff0c;属于面向对象的动态类型语言&#xff0c;最初被设计用于编写自动化脚本&#xff0c;随着版本的不断更新和语言新功能的添加&#xff0c;越来越多被用于独立的、大型项目的开发。 python前景…

挖掘用户真实需求 避坑5大技巧

1、用户总是“说一套 做一套” 在进行需求市场调研时&#xff0c;我们问1000个用户&#xff0c;是否想要黄金&#xff0c;几乎所有的用户都是肯定的回答。如果我们以此认定&#xff0c;黄金是用户的真实需求会过于草率。 我们需继续追问用户&#xff1a;买黄金做什么&#xff1…

【微信小程序开发】第 7 课 - 小程序的组件

欢迎来到博主 Apeiron 的博客&#xff0c;祝您旅程愉快 &#xff01; 时止则止&#xff0c;时行则行。动静不失其时&#xff0c;其道光明。 目录 1、缘起 2、小程序中组件的分类 3、常用的视图容器类组件 3.1、view 组件 3.2、scroll - view 组件 3.3、swiper 和 swiper…

【youcans动手学模型】Xception 模型-CIFAR10图像分类

欢迎关注『youcans动手学模型』系列 本专栏内容和资源同步到 GitHub/youcans 【youcans动手学模型】Xception 模型-CIFAR10图像分类 1. Xception 神经网络模型1.1 模型简介1.2 论文介绍1.3 分析与讨论 2. 在 PyTorch 中定义 Xception 模型类2.1 深度可分离卷积2.2 带残差连接的…

【九章斩题录】从尾到头打印链表(JZ6)

精品题解 &#x1f525; 《九章斩题录》 &#x1f448; 猛戳订阅 目录 JZ6 - 从尾到头打印链表 「 法一 」链表元素存入数组后再反转 「 法二 」递归大法 「 法三 」栈 JZ6 - 从尾到头打印链表 &#x1f4da; 题目&#xff1a;输入一个链表的头节点&#xff0c;按链表从…

苹果iOS 17新功能:重置密码 72 小时内可使用旧密码再次重置

一些用户可能会遇到&#xff0c;在修改了 iPhone 密码之后&#xff0c;突然忘记新密码的情况。现在苹果在 iOS 17 中加入了新的解决方案&#xff1a;在重置密码的 72 小时之内&#xff0c;如果用户不小心忘记了新设置的密码&#xff0c;仍然可以使用旧密码进行再次重置。 在重…

TS系列之工具类型Partial、Required、Pick、Record详解,示例

TS系列之工具类型Partial、Required、Pick、Record详解&#xff0c;示例 文章目录 前言一、Partial<Type>二、Required<Type>三、Pick<Type, Keys>四、Record<Keys, Type>总结 前言 本片文章主要利用上一篇所学的keyof做一个延申与扩展的学习。在Type…

OpenGaussDB2.0.1

目录 1. GaussDB版本2. OpenGaussDB介绍3. 单节点安装3.1 环境配置3.2 安装 4. 远程连接设置 1. GaussDB版本 GaussDB的版本&#xff1a; GaussDB 100&#xff1a;目前暂不发布&#xff0c;公司合作伙伴需向华为提交申请&#xff08;GaussDB 100 将在 2020 年被正式命名为 Gaus…

Redis入门 - 3种特殊数据类型

原文首更地址&#xff0c;阅读效果更佳&#xff01; Redis入门 - 3种特殊数据类型 | CoderMast编程桅杆https://www.codermast.com/database/redis/three-special-datatype.html 在我们平常的业务中基本只会使用到Redis的基本数据类型&#xff08;String、List、Hash、Set、S…