【Reading Notes】cp8-Screeen Effects with Unity Render Textures

本教程介绍了如何使用Unity创建屏幕效果,包括亮度、饱和度、对比度调节,以及使用不同的混合模式如叠加融合。

写在前面

在本章节,将学习一下内容:
+ 建立简单的screen effects 脚本系统
+ 使在screen effects中用亮度,饱和度,对比度
+ 实现照片风格的screen effects
+ 在screen effect中使用Overlay Blend

介绍

要想更深刻地理解着色器的过程,是去创建我们自己的着色器,同样对于screen effects。通过编写screen effects,可以创造惊人的实时相片效果:Bloom(泛光),Motion Blur(运动模糊),HDR等等。现代游戏中很多都大量运用了这些手法,深度效果,泛光效果,甚至是颜色矫正。通过本章节的学习,将指导如果创建脚本系统去控制我们的screen effects。将了解Render Texture, 什么是depth buffer(深度缓冲区),如果通过脚本控制最好的渲染效果。

Setting up the screen effect script system(设置脚本系统)

创建screen effects过程的其中一步是抓取一张全屏的纹理,使用Shader去对它进行逐像素处理(GPU中),最后把它重新发送给Unity的渲染器。这允许我们对游戏的最后渲染结果执行实时的逐像素操作,赋予了我们更多的全局的艺术创作空间。想象你必须对游戏场景中的每个对象的材质做调整才能实现最后的效果。当这操作性极低,因为这回花费大量的时间去实行。通过利用screen effect,我们可以对最后的显示效果做整体的调整。

为了使Screen effect 系统运行,需要创建一个脚本去获得当前帧的渲染结果(Render Texture),通过脚本给shader传递Render Texture,就可以实现易于使用的screen effect系统了。首先,我们先创建一个十分简单的grayscale effect,调整渲染的黑白效果。

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

//1.为了让脚本在编辑器状态下也可以执行,使用属性[ExecuteInEditMode]
[ExecuteInEditMode]
public class TestRenderImageMe : MonoBehaviour {

    public Shader curShader;
    public float grayScaleAmount = 1.0f;
    private Material curMaterial;

    Material material
    {
        get
        {
            if(curMaterial == null)
            {
                curMaterial = new Material(curShader);
                curMaterial.hideFlags = HideFlags.HideAndDontSave;
            }
            return curMaterial;
        }
    }

    // Use this for initialization
    void Start () {
        if(!SystemInfo.supportsImageEffects)
        {
            enabled = false;
            return;
        }

        if(!curShader && !curShader.isSupported)
        {
            enabled = false;
        }
    }

    void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if(curShader != null)
        {
            material.SetFloat("_LuminosityAmount", grayScaleAmount);
            Graphics.Blit(source, destination, material);
        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }

    private void Update()
    {
        grayScaleAmount = Mathf.Clamp(grayScaleAmount, 0.0f, 1.0f);
    }

    private void OnDisable()
    {
        if(curMaterial)
        {
            DestroyImmediate(curMaterial);
        }
    }
}
  • 为了使脚本可以在编辑状态下执行,使用了[ExecuteInEditMode]属性
  • 为脚本定义了几个变量保存当前使用的Shdaer,Material,调整的参数
  • 在start中对平台是否支持ImageEffects进行判断,使脚本在不同平台不至于报错
  • 重载OnRenderImage(),这是一个Unity内置的函数,可以抓取Unity当前的Render Texture。而Graphic.Blit()函数,像Unity渲染器传递我们的处理结果。
Shader "CookbookShaders/self/ImageEffect" {
    Properties {
        _MainTex("Base (RGB)", 2D) = "white" {}
        _LuminosityAmount("GrayScale Amount", Range(0.0, 1)) = 1.0
    }

        SubShader{
            Pass
            {
                CGPROGRAM
                #pragma vertex vert_img
                #pragma fragment frag
                #pragma fragmentoption ARB_precision_hint_fastest
                #include "UnityCG.cginc"

                uniform sampler2D _MainTex;
                fixed _LuminosityAmount;

                fixed4 frag(v2f_img i):COLOR
                {
                    fixed4 renderTex = tex2D(_MainTex, i.uv);
                    float luminosity = 0.299 * renderTex.r + 0.587 * renderTex.g + 0.114 * renderTex.b;
                    fixed4 finalColor = lerp(renderTex, luminosity, _LuminosityAmount);

                    return finalColor;
                }


                ENDCG
            }
    }
    FallBack "Diffuse"
}
  • 定义了_LuminosityAmount 对灰度程度进行控制
  • frag中进行screen effect的处理步骤,对于0.299 r, 0.587 g, 0.144 b,这几个数值是是图像显示成灰度的经验值。
  • 通过CG的插值函数,利用_LuminosityAmount在源颜色和灰度之间进行插值得到最后颜色

image
图片来自cookbook书中截图。

Depth Texture

我们的基本Screen effect的框架已经搭建起来,下面来实现一些更有用的效果。

private void Update()
{
    Camear.main.depthTextureMode = DepthTextureMode.Depth;
    depth_power = Mathf.Clamp(depth_power, 0.0f, 5.0f);
}

修改了Update()函数,设置主摄像机的depthTextureMode模式为Depth, 这将使主摄像机渲染深度图到Shader的内置变量_CameraDepthTexture

Shader "CookbookShaders/self/SceneDepth_Effect"
{
    Properties
    {
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _DepthPower("Depth Power", Range(1, 5)) = 1
    }
        SubShader
        {

            Pass
            {
                CGPROGRAM
                #pragma vertex vert_img
                #pragma fragment frag
                #pragma fragmentoption ARB_precision_hint_fastest
                #include "UnityCG.cginc"

                uniform sampler2D _MainTex;
                fixed _DepthPower;
                sampler2D _CameraDepthTexture;

                float4 frag(v2f_img i):COLOR
                {
                    float depth = UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, i.uv.xy));
                    depth = Linear01Depth(depth);
                    return fixed4(depth,depth,depth,1);
                }
                ENDCG
            }
    }
}
  1. 对内置的_CameraDepthTexture进行采样,使用UNITY_SAMPLE_DEPTH取得深度值,这个宏大部分情况下只是直接去了采样之后的r值。
  2. Linear01Depth(depth)把深度值转化为01的的范围,这里有个值得注意的地方,如果摄像机设置的Far(能看见的最远距离)很大,那么在[0-100]这些范围转化到01范围基本就是0-0.1,基本就是黑色的,看不出什么渐变效果。需要对Far进行合理设置以便场景中的深度更均匀的分布在0-1区间内。
    image

Using brightness(亮度)、saturation(饱和度)、contrast(对比度)

使用screen effect对最后的颜色进行调整,最重要的是提供更多的全局控制选项给艺术家。例如调整颜色比例的sliders。在这一小节,将对RenderTexture实现更多的颜色操作。这些包括brightness(亮度)、saturation(饱和度)、contrast(对比度)。学习如何对这些颜色调整进行编码,给了我们一个很好的基础去学习screen effect的艺术。

scripts部分,还是单纯的将控制变量传递给着色器

void OnRenderImage(RenderTexture source, RenderTexture destination)
{
    if (curShader != null)
    {
        material.SetFloat("_BrightnessAmount", brightnessAmount);
        material.SetFloat("_satAmount", saturationAmount);
        material.SetFloat("_conAmount", contrastAmount);
        Graphics.Blit(source, destination, material);
    }
    else
    {
        Graphics.Blit(source, destination);
    }
}

shader部分

Shader "CookbookShaders/self/BSC_Effect"
{
    Properties
    {
        _MainTex("Base (RGB)", 2D) = "white" {}
        _BrightnessAmount("Brightness Amount", Range(0.0, 1)) = 1.0
        _satAmount("Saturation Amount", Range(0.0, 1)) = 1.0
        _conAmount("Contrast Amount", Range(0.0, 1)) = 1.0
    }
    SubShader
    {

        Pass
        {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag
            #pragma fragmentoption ARB_precision_hint_fastest
            #include "UnityCG.cginc"

            uniform sampler2D _MainTex;
            fixed _BrightnessAmount;
            fixed _satAmount;
            fixed _conAmount;


            float3 ContrastSaturationBrightness(float3 color, float brt, float sat, float con)
            {
                float AvgLumR = 0.5;
                float AvgLumG = 0.5;
                float AvgLumB = 0.5;

                //CIE颜色系统标准灰度系数
                float3 LuminanceCoeff = float3(0.2125, 0.7154, 0.0721);

                float3 AvgLumin = float3(AvgLumR, AvgLumG, AvgLumB);
                float3 brtColor = color * brt;
                //计算像素对应的灰度值
                float intensityf = dot(brtColor, LuminanceCoeff);
                float3 intensity = float3(intensityf, intensityf, intensityf);

                //使用饱和度变量,在灰度值和亮度颜色中插值
                float3 satColor = lerp(intensity, brtColor, sat);

                //使用对比度变量,在平均无对比和上一步得出的计算结果插值
                //0.5 * (1-con) + col * con = 0.5 + (col - 0.5) * con
                //线性插值,可以看出当con > 1的时候最后的颜色值变得更亮要看col比0.5要大还是小。
                //这就使得亮的颜色更亮,暗的颜色更暗,达到调整对比度的效果。
                float3 conColor = lerp(AvgLumin, satColor, con);
                return conColor;
            }

            fixed4 frag(v2f_img i) :COLOR
            {
                fixed4 renderTex = tex2D(_MainTex, i.uv);
                renderTex.rgb = ContrastSaturationBrightness(renderTex.rgb,
                                            _BrightnessAmount,
                                        _satAmount,
                                        _conAmount);
                return renderTex;
            }
            ENDCG
        }
    }
}

image

亮度:0.5 饱和度:2 对比度:1.75

Using basic Photoshop-like Blend modes

screen effect 不仅仅只是能对渲染结果的颜色做简单的调整,还可以把Render Texture和别的Texture做混合。这种技术其实和Photoshop里面的图层叠加没什么区别。这是非常对于游戏艺术家来说,提供了一个游戏中模拟混合模式的环境,而不是通过Photoshop。

在这一小节,我们将实践多种混合方式MultiplyAddOverlay

Shader "CookbookShaders/self/BlendMode_Effect"
{
    Properties
    {
        _MainTex("Base (RGB)", 2D) = "white" {}
        _BlendTex("Blend Texture", 2D) = "white" {}
        _Opacity("Blend Opacity", Range(0.0, 1)) = 1.0
    }

    SubShader
    {

        Pass
        {
            CGPROGRAM
    #pragma vertex vert_img
    #pragma fragment frag
    #pragma fragmentoption ARB_precision_hint_fastest
    #include "UnityCG.cginc"

            uniform sampler2D _MainTex;
            uniform sampler2D _BlendTex;
            fixed _Opacity;
            fixed4 frag(v2f_img i) :COLOR
            {
                fixed4 renderTex = tex2D(_MainTex, i.uv);
                fixed4 blendTex = tex2D(_BlendTex, i.uv);
                //fixed4 blendedMultiply = renderTex * blendTex; //Multiply
                //fixed4 blendedMultiply = renderTex + blendTex; //Add
                fixed4 blendedMultiply = 1 - (1.0 - renderTex) * (1.0 - blendTex); //Screen Blend
                renderTex = lerp(renderTex, blendedMultiply, _Opacity);
                return blendedMultiply;
            }
            ENDCG
        }
    }
}

效果:从左往右分别是Mulity、Add、Screen Blend
image

Using the Overlay Bend mode

其实只是简单的当颜色分量小于0.5的时候使用Multiply Mode,大于0.5的时候使用Screen Blend。这里测试的贴图不太好,并没有得到很好的结果。

image

The End~~~~

<think>我们正在处理一个大战飞机项目中的错误:NameError: name 'screen' is not defined 根据引用[1]和[2],这类错误通常是因为变量在使用前未被定义。在Python中,这可能是由于: 1. 变量名拼写错误 2. 变量在使用前确实没有声明或赋值 3. 作用域问题(例如在函数内部定义的变量,在外部使用) 在游戏项目中,'screen'通常代表游戏窗口。在Pygame中,我们通常通过调用`pygame.display.set_mode()`来创建屏幕(screen)对象。 因此,解决步骤可能包括: 1. 确保已经正确导入pygame模块 2. 检查是否创建了名为'screen'的变量(即调用`pygame.display.set_mode()`) 3. 检查'screen'变量的作用域,确保在使用它的地方(如绘制图形时)可以访问到 另外,根据错误信息,错误发生在使用'screen'的地方。所以我们需要检查代码中所有使用'screen'的地方,确保在第一次使用之前已经定义。 下面是一个典型的Pygame初始化代码片段: ```python import pygame pygame.init() # 创建屏幕对象 screen = pygame.display.set_mode((800, 600)) ``` 如果代码中其他地方(比如在函数中)使用了'screen',但'screen'是在全局作用域定义的,那么在函数内部需要使用`global screen`声明,或者将screen作为参数传递给函数。 但是,根据错误信息,我们无法直接看到代码,所以需要用户检查这些点。 另外,如果项目较大,可能有多个文件,那么需要检查'screen'是否在需要的模块中定义并导出(例如通过import导入)。 根据以上分析,给出解决方案: 步骤1:检查是否在代码开头正确导入了pygame 步骤2:检查是否在代码中创建了screen变量(通常是在初始化部分) 步骤3:如果创建screen的代码在函数内部,那么需要确保在函数外部也能访问,或者将screen作为参数传递 步骤4:检查变量名拼写,确保所有使用的地方都是'screen'(注意大小写) 如果用户是在某个函数中遇到这个错误,比如: ```python def draw(): screen.fill((0,0,0)) # 这里报错,因为screen在函数内部没有定义 ``` 那么有两种修改方式: 方式一:在函数内部使用全局变量(如果screen是全局变量) ```python def draw(): global screen screen.fill((0,0,0)) ``` 方式二:将screen作为参数传递 ```python def draw(screen): screen.fill((0,0,0)) # 调用时 draw(screen) ``` 根据项目结构,推荐使用方式二,因为这样更清晰,且避免使用全局变量。 最后,如果用户按照以上步骤检查后仍然无法解决,建议提供相关代码片段以便进一步诊断。 现在,我们生成相关问题: §§相关问题§§ 1. 大战飞机项目中,如何正确初始化Pygame的屏幕(screen)? 2. 在Pygame中,如何解决函数内部访问全局变量'screen'的问题? 3. 如何避免在Pygame项目中出现变量未定义的错误?</think>### 大战飞机项目中解决 `NameError: name 'screen' is not defined` 错误的方法 在Python游戏开发中,`NameError: name 'screen' is not defined` 错误表示代码尝试使用 `screen` 变量但该变量未被定义。这通常发生在Pygame等游戏库的使用过程中。以下是完整的解决步骤: #### 1. **检查变量定义** ```python # 错误示例:直接使用未定义的screen screen.blit(player_image, (x, y)) # 报错位置 # 正确做法:在main函数或全局作用域定义screen import pygame pygame.init() screen = pygame.display.set_mode((800, 600)) # 必须定义 ``` #### 2. **确认作用域** ```python # 错误示例:在函数内定义但未传递 def init_game(): screen = pygame.display.set_mode((800, 600)) # 局部变量 def draw(): screen.blit(...) # 报错:无法访问局部变量 # 解决方案1:使用全局声明 def init_game(): global screen # 声明为全局变量 screen = pygame.display.set_mode((800, 600)) # 解决方案2:通过参数传递 def draw(screen): # 接收参数 screen.blit(...) ``` #### 3. **检查导入顺序** ```python # 错误示例:在pygame初始化前使用 print(screen.get_width()) # 报错:screen未定义 pygame.init() screen = pygame.display.set_mode((800, 600)) # 正确顺序:先初始化再使用 pygame.init() screen = pygame.display.set_mode((800, 600)) print(screen.get_width()) # 正常执行 ``` #### 4. **处理模块化结构** 若项目有多个文件: ```python # main.py import game_logic screen = pygame.display.set_mode((800, 600)) game_logic.draw(screen) # 显式传递参数 # game_logic.py def draw(screen): # 显式接收参数 screen.blit(...) ``` #### 5. **完整代码框架** ```python import pygame pygame.init() # 1. 定义screen screen = pygame.display.set_mode((800, 600)) pygame.display.set_caption("大战飞机") # 2. 游戏循环 running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # 3. 正确传递screen screen.fill((0, 0, 0)) # 清屏 # ...绘制逻辑... pygame.display.flip() pygame.quit() ``` ### 关键检查点 1. 在首次使用 `screen` 前是否执行了 `pygame.display.set_mode()` 2. 是否在函数内修改全局变量时使用了 `global screen` 3. 跨文件调用时是否显式传递了 `screen` 参数 4. 确保所有代码路径中 `screen` 都被正确定义 ### 调试建议 - 在报错位置前添加 `print("Screen defined:", 'screen' in globals())` - 使用IDE的调试器检查变量作用域 - 确保没有拼写错误(如 `scren` 或 `screeen`) 这类错误通常是由于变量作用域管理不当造成的,通过显式传递参数或规范使用全局变量可以彻底解决[^1][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值