http://www.adobe.com/devnet/flash/articles/pixel_bender_basics.html?devcon=f2c
원문의 모든 swf는 스크린샷으로 찍어서 올립니다. 슬라이더를 건드려 보려면 원문으로 가세요.


어도비 픽셀 벤더로 블렌드, 필터, 칠 제작하기

픽셀 벤더는 어도비 플래시 플레이어 10, 어도비 애프터 이펙트에서 지원하고, 머지않아 어도비 포토샵에서 지원할 그래픽 처리 엔진입니다. 언어는 3D 렌더링에서 픽셀 그리기 연산을 최적화하기 위해 사용하는 OpenGL Shading Language(GLSL)같은 단편 셰이더 언어를 기반으로 합니다. 플래시 플레이어에서 여러분은 필터, 블렌드, 영역 채우기, 선 채우기를 제작하는데 픽셀 벤더 프로그램을 사용할 수 있습니다.
벤더는 구부리개라는 뜻입니다. fragment shader는 '픽셀 수준으로 처리하는 셰이더'로 OpenGL 용어이며 같은 기술을 DirectX에서는 pixel shader라고 합니다.

이미지, 벡터 그래픽, 심지어 디지털 비디오를 포함하여 어느 표시 객체에든 픽셀 벤더 효과를 적용할 수 있습니다. 실행 속도는 매우 빠릅니다. 액션스크립트로는 실행하는 데 한 프레임마다 몇 초나 걸리던 효과를 실시간으로 할 수 있습니다.

사용자 삽입 이미지

어도비 픽셀 벤더 툴킷을 사용하여 커널(kernel)이라고 하는 픽셀 벤더 프로그램을 제작하고 컴파일합니다. 툴킷으로 생산한 컴파일된 바이트코드는 Shader 객체로 불러와 여러분의 SWF 컨텐트에서 사용할 수 있습니다. 픽셀 벤더 툴킷은 플래시 CS4 프로페셔널을 깔면 자동으로 깔립니다.

어도비 플래시 CS4 프로페셔널에서 픽셀 벤더를 사용하는 것에 대한 문서는 프로그래밍 액션스크립트 3.0Working with Pixel Bender shaders장 그리고 액션스크립트 구성 요소와 언어 레퍼런스의 Shader 클래스 부분에서 볼 수 있습니다. 이 문서에는 플래시 CS4에서 픽셀 벤더와 함께 사용할 수 있는 객체들에 관한 자세한 설명이 포함되어 있습니다. 픽셀 벤더 언어에 대한 문서는 픽셀 벤더 툴킷의 Help 메뉴에서 볼 수 있습니다.

몇 새로운 기교들을 배우는 것뿐만 아니라 뭐가 가능한지도 보고 싶다면 픽셀 벤더 프로그램에 관한 연습 예제들을 추천합니다. 이 글에서 제공하는 예제들 외에도 어도비가 운영하는 Pixel Bender Exchange에서 공용 커널 보고를 볼 수 있습니다. 이 커널 프로그램들의 제작자들은 여러 플래시 개발자 커뮤니티와 공유하는 것을 기꺼이 동의했습니다. 여러분의 커널이 다음으로 게시될지도? 픽셀 벤더 프로그래밍에 대해 토론하는 포럼도 있습니다.


필요물

이 글의 많은 제작물에 다음 소프트웨어와 파일이 필요합니다.

플래시 플레이어 10
내려받기

플래시 CS4 프로페셔널
체험
구입

픽셀 벤더 툴킷
정보

샘플 파일
pixel_bender_basics.zip (ZIP, 3 MB)

이 글의 예제들에는 다음 프로젝트들이 포함되어 있습니다.

  • ChannelScrambler
  • CheckerFill
  • GaussianBlur
  • GrainBlend
  • HardLightBlend
  • MultiplyBlend
  • ScreenBlend

이 글에 각 예제의 SWF 컨텐트가 표시됩니다. 예제들을 더 자세하게 조사하려면 샘플 파일을 받아 픽셀 벤더 툴킷과 플래시 CS4 프로페셔널에서 여세요.


미리 필요한 지식

플래시 CS4 프로페셔널 또는 액션스크립트로 애플리케이션을 제작해 본 총체적인 경험이 필요합니다. 이 글의 예제들은 플래시 CS4 프로페셔널로 제작했습니다. 코드는 code라는 레이어의 1프레임에 있습니다. 컨트롤들과 셰이더를 적용할 객체들은 "display"라는 레이어에 있습니다. 플래시 CS4를 사용하지 않는다면 액션스크립트 또는 어도비 플렉스에서 여러분의 표시 객체와 컨트롤을 준비하여 샘플 코드를 개조할 수 있습니다.


저자에 대해

Charles Ward는 새로운 기술을 생각하는 것을 좋아하는 기술서 작가입니다. 이전에는 Falcon 3.0과 4.0, Star Trek: A Final Unity 같은 기공식 컴퓨터 게임 제작에 기여했습니다. Charles는 집필을 하지 않을 때는 자유다이빙을 하거나 자식들과 놉니다.


픽셀 벤더 언어 개요

커널 코딩으로 뛰어들기 전에 잠시 픽셀 벤더 언어를 살펴봅시다. 이 글을 읽어도 이 언어에 전문가가 되지는 않겠지만 다음 설명은 픽셀 벤더 언어로 작성한 프로그램을 이해하는 데 도움이 될 겁니다. 더 자세한 것은 픽셀 벤더 툴킷 애플리케이션의 Help 메뉴의 Adobe Pixel Bender Language 1.0 Tutorial and Reference Guide를 보세요.

픽셀 벤더는 C, 자바, 액션스크립트 같은 언어와 비슷한 절차적 구문을 사용합니다. 픽셀 벤더에는 내장 데이터 타입들과 이미지 처리를 위한 함수들이 포함됩니다. 이미 액션스크립트에 능숙하다면 픽셀 벤더 커널 작성을 상당히 수월하게 배울 수 있을 겁니다. 다음은 액션스크립트와 픽셀 벤더 언어 구문의 가장 중요한 차이점들입니다.

• 변수에 대한 타입 선언은 변수 이름의 뒤가 아니라 앞에 옵니다. var 키워드는 사용하지 않습니다. 그러므로 이렇게 정수값을 선언하는 대신
 var foo:int;
이런 구문을 사용합니다.
 int foo;

• 런타임에 새로운 객체를 생성할 수 없습니다. 그러므로 new 키워드는 지원되지 않고, 필요하지도 않습니다.

• 픽셀 벤더는 실수를 나타내는 데 Number 데이터 타입 대신 float 데이터 타입을 사용합니다. 용어 float은 떠다니는 점을 의미합니다(소수점이 수의 어느 위치로든 "떠다닐 수 있음"을 뜻합니다).

• 픽셀 벤더 프로그램에서는 실수를 입력할 때 반드시 소수점을 포함해야 합니다. 그렇지 않으면 픽셀 벤더 컴파일러는 그 수를 실수가 아니라 정수로 다룰 것입니다. 액션스크립트와 달리 픽셀 벤더는 암묵적인 데이터 변환을 하지 않습니다. 예를 들어 1.0 대신 1을 입력하면 픽셀 벤더 툴킷에게서 난해한 에러 메시지를 받을 것입니다.

• 픽셀 벤더에는 벡터 데이터 타입이 내장되어 있습니다. 기본 타입 이름의 뒤에 붙은 숫자로 벡터 타입을 구별할 수 있습니다. 예를 들어 float3 타입은 float 타입인 세 요소를 포함하는 벡터입니다.

• 픽셀 벤더에서는 벡터 데이터 타입을 보통 다음 구문처럼 사용합니다.
 float4 rgbaPixel = float4( 1.0, 0.3, 0.2, 0.8 );

• 위의 구문은 rgbaPixel이라는 벡터 변수를 선언하고 거기에 색상값을 할당합니다. 표현식 float4( 1.0, 0.3, 0.2, 0.8 )은 글자 그대로 float4 벡터 상수를 정의하며 이것은 rgbaPixel 변수에 할당됩니다.

• 픽셀 벤더는 벡터 휘젓기를 지원합니다.
점 표기와 세 요소 이름 집합(r,g,b,a 또는 x,y,z,w 또는 s,t,p,q) 중 한 집합을 사용하여 벡터의 멤버에 접근할 수 있습니다. 휘젓기로 요소 이름들을 재정렬하여 쉽게 재배열할 수 있습니다. 예를 들어 다음 구문은 픽셀 값을 다른 변수에 할당할 때 픽셀 벡터의 적색 채널과 녹색 채널을 바꿉니다.
 pixel4 mixedUp = rgbaPixel.grba;

한 채널을 반복할 수도 있고
 pixel4 allRed = rgbaPixel.rrra;

채널을 버릴 수도 있습니다.
 pixel2 redAndBlue = rgbaPixel.rb;

주의: 벡터 변수와 함께 사용할 요소 이름 모음은 여러분 마음대로 해도 됩니다. 예를 들어 myVector.rmyVector.x와 같습니다. 색상에 rgba 모음을, 위치에 xyzw 또는 stpq 모음을 사용하는 것은 좋은 습관입니다. 하나의 참조에서 둘 이상의 이름 모음을 섞을 수는 없습니다.
rgbaPixel.xpba 같은 표현식은 안 된다는 뜻입니다.

• 픽셀 벤더는 내장 행렬 데이터 타입도 지원합니다. 행렬 데이터 타입은 벡터와 비슷하지만 수의 이차원 배열을 포함합니다. 예를 들어 float3x3 행렬 타입은 각각 요소가 셋인 세 벡터를 포함합니다.

• 플래시 플레이어에서 픽셀 벤더는 루프, 커스텀 함수(evaluatePixel() 함수는 제외), 배열을 지원하지 않습니다.

주의: 플래시 플레이어를 위한 픽셀 벤더 커널을 개발할 때는 Turn on Flash Player Warnings and Errors 옵션(툴킷 창의 Build 메뉴에 있습니다)을 켜세요. 이 옵션을 켜면 컴파일러는 여러분이 플래시 플레이어에서 지원되지 않는 픽셀 벤더 언어 특성을 사용할 때 즉시 알려줄 것입니다. (그렇지 않으면 플래시 플레이어을 위해 커널을 내보내려 하기 전까지 에러를 못 볼 것입니다.)


커널 예행 연습

일반적인 픽셀 벤더 커널은 다음 작업들을 수행합니다.

• 입력 이미지에서 픽셀을 추출한다
• 추출한 픽셀 색상에 연산을 가한다
• 수정한 값을 출력 픽셀에 할당한다

다음의 간단한 픽셀 벤더 커널은 위 작업들을 수행합니다. 이 프로그램은 ChannelScrambler라는 커널을 정의합니다.

<languageVersion : 1.0;>

kernel ChannelScramblerFilter
<   namespace : "com.adobe.example";
    vendor : "Adobe Systems Inc.";
    version : 1;
    description : "Changes the color channel order from rgba to gbra.";
>
{
    input image4 inputImage;
    output pixel4 outPixel;

    void evaluatePixel()
    {
        pixel4 samplePixel = sampleNearest( inputImage, outCoord() );
        outPixel = samplePixel.gbra;
    }
}


이 커널은 inputImage라는 입력 이미지와 outPixel이라는 출력 픽셀을 선언합니다. evaluatePixel() 함수 안에서는, 지금 처리되고 있는 출력 좌표에 있는 픽셀에 두 내장 함수 sampleNearest()outCoord()를 사용하여 접근합니다. 그 다음, 추출된 픽셀을 색상 채널들을 재정렬하는 휘젓기를 사용하여 outPixel 변수에 할당합니다.

플래시 CS4에서 제작한 SWF 파일에서 이 커널을 필터로 쓰면 다음 결과가 나옵니다.

사용자 삽입 이미지
픽셀 벤더 프로그램에 필요한 요소로는 languageVersion 태그와
 <languageVersion: 1.0;>

커널 이름 선언이 있습니다.
 kernel ChannelScrambler{...}

커널 안에는 output pixel4 outPixel 같은 출력 선언과 evaluatePixel() 함수가 하나씩 있어야 합니다. 한 커널은 (아무 입력도 없는 것을 포함하여) 입력을 몇 개든 가질 수 있습니다. 하지만 플래시 플레이어에서는 커널을 사용하는 방법에 따라 입력의 개수가 정해집니다. 블렌드로서 사용되는 셰이더는 입력 두 개, 필터로서 사용되는 셰이더는 입력 한 개가 필요하고, 칠로서 사용되는 셰이더는 아무 입력도 필요하지 않습니다.

픽셀 벤더 커널은 출력 이미지의 모든 픽셀에 대해 한 번씩 실행됩니다. 실행 사이에는 어떤 상태 정보도 저장되지 않으므로 각 실행에서 추출된 픽셀을 누적하여 평균 픽셀 값을 모을 수 없습니다. 액션스크립트로 정보를 미리 계산할 수 있고 입력 또는 매개변수로서 넘길 수 있다 하더라도 커널의 각 실행에선 필요한 모든 정보를 계산해야 합니다.


추출

입력 이미지 안의 픽셀 값에 접근하려면 추출 함수를 사용해야 합니다. 다음 내장 함수들로 추출합니다.
sampleNearest() 기입한 좌표와 가장 가까운 픽셀의 채널 값들을 포함하는 벡터를 반환합니다. (약간 다르게 작동하는 sampleLinear() 함수도 있습니다.)
outCoord() 현재 출력 픽셀의 좌표를 반환합니다.

픽셀 벤더는 이미지를 처리할 때 출력 이미지의 모든 픽셀에 대해 커널을 실행합니다. outCoord() 함수는 현재 픽셀의 좌표를 반환합니다. 픽셀 벤더의 좌표 시스템은 플래시 플레이어와 비슷합니다. 원점은 왼쪽 위 모서리입니다. 오른쪽 아래로 갈 수록 양으로 증가합니다. 픽셀은 모두 정사각형입니다.

outCoord() 위치의 픽셀만을 추출해야 하는 건 아닙니다. 예를 들어 현재 픽셀에서 오른쪽으로 10픽셀, 밑으로 5픽셀 떨어진 픽셀을 추출하기 위해 다음의 픽셀 구문을 사용할 수 있습니다.
 pixel4 inputPixel = sampleNearest( inputImage, outCoord() + float2( 10.0, 5.0 ) );

표현식 outCoord() + float2( 10.0, 5.0 )outCoord() 함수로 만들어 낸 좌표 벡터에 요소가 두 개인 벡터를 더합니다. 이것과 동등한 방법입니다.
 float2(outCoord().x + 10.0, outCoord().y + 5.0)

추출 좌표가 입력 이미지의 범위를 넘어간다면 0만 포함하는 색상 벡터가 반환됩니다. 예를 들어 입력의 타입이 image4이라면, 외부 픽셀을 추출하면 완전히 투명한 검은 픽셀이 반환될 겁니다. 입력의 타입이 image3이라면 검은 픽셀(알파 채널이 없는)이 반환될 겁니다. 이론상 픽셀 벤더 좌표 공간은 무한히 뻗어 나갑니다. 물론 표현할 수 있는 좌표 범위는 실질적인 한계가 있고, 없는 정보를 추출해봤자 유용하지도 않습니다.


픽셀로 작업하기

픽셀을 표현하는 벡터가 있다면, 색상 값으로 작업하는 방법은 많습니다. 예를 들어 pix이라는 pixel4 변수가 있다면 다음 방법들로 개개의 색상 채널에 접근하거나 채널들을 결합시킬 수 있습니다.

• 적색 채널: pix.r 또는 pix[0]
• 녹색 채널: pix.g 또는 pix[1]
• 청색 채널: pix.b 또는 pix[2]
• 알파 채널: pix.a 또는 pix[3]
• 적색 채널과 알파 채널: pix.ra
• 적색 채널과 청색 채널이 바뀌고 알파 채널이 없는 색상 채널: pix.bgr

한 색상 채널은 32 비트 실수고 보통 0.0(검은색)에서 1.0(흰색) 사이입니다. 출력 색상 값은 이 범위를 벗어날 수 있지만 그려지는 모습은 같을 것입니다. 다르게 말하자면 비트맵으로서 그려질 때 pixel3(-1.0, -1.0, -1.0, -1.0)pixel3(0.0, 0.0, 0.0, 0.0)과 같은 검정입니다. (하지만 한 이미지에 여러 필터를 먹이면 두 번째 필터는 이 픽셀을 추출할 때 pixel3(0.0, 0.0, 0.0, 0.0)이 아니라 pixel3(-1.0, -1.0, -1.0, -1.0)을 얻을 것이기 때문에 이 차이는 중요해질 수 있습니다.)

픽셀 벡터에 스칼라 또는 벡터 값으로 연산을 수행할 수 있습니다. 스칼라 값을 사용하면 각 채널에 연산이 가해집니다. 예를 들어 다음 연산은 각 채널의 값을 반절로 나눕니다(알파 채널을 포함하여).
 pixel4 pix = sampleNearest( inputImage, outCoord());
 pix = pix / 2.0;


벡터를 사용하여 같은 작업을 작성할 수 있습니다.
 pix = pix * pixel4( 0.5, 0.5, 0.5, 0.5 );

픽셀 벤더 이미지가 채널 당 32 비트를 가진다 하더라도 플래시 CS4의 그래픽은 채널 당 8 비트만을 가집니다. 플래시 플레이어에서 커널을 돌릴 때 입력 이미지 데이터는 채널 당 32 비트로 변환되고 커널 실행이 끝나면 채널 당 8비트로 다시 변환됩니다.


입력 정의하기

입력은 input 키워드로 선언합니다.

input image4 sourceImage;

image1, image2, image3, image4 데이터 타입을 사용하여 입력을 선언할 수 있습니다. 한 커널에서 서로 다른 입력은 채널 수가 다를 수 있습니다. 셰이더로 만들어 낸 출력 이미지의 채널 수는 입력의 데이터 타입이 아니라 출력 픽셀의 데이터 타입에 의해 결정됩니다.

플래시 플레이어에서 커널을 쓰는 데 필요한 입력의 수보다 많은 입력을 선언할 수 있습니다. 하지만 플래시 플레이어는 필요한 입력에만 이미지 데이터를 자동으로 할당할 것입니다. 커널을 블렌드, 필터, 칠로서 할당하기 전에 여러분은 이미지를 여분의 입력에 스스로 할당해야 합니다. 예를 들어 여러분의 필터 커널이 질감 효과를 만드는 데 추가적인 이미지를 사용한다면, 그 커널을 포함하는 ShaderFilter 객체를 표시 객체의 필터 배열에 할당하기 전에 커널에 질감을 입력으로서 할당해야 할 겁니다(이 글의 뒷부분에 그 방법이 나옵니다).

매개변수 정의하기

커널에 입력 이미지가 아닌 다른 값도 매개변수로 넘길 수 있습니다. 매개변수는 parameter 키워드로 선언하며 이미지(또는 플래시 플레이어를 위해 작성한 커널에선 어떻게 해도 쓸 수 없는 영역(region))를 제외하고 어느 데이터 타입이든 될 수 있습니다. 여러분은 기본값, 최솟값, 최댓값을 명시하기 위해 매개변수에 대해 메타데이터를 선언할 수 있습니다. 또한 설명을 달 수도 있습니다. 메타데이터는 각괄호(<>) 사이에 선언되며 플래시 플레이어에서 접근할 수 있습니다. 매개변수에 적당한 기본값을 정의하는 것은 언제나 좋은 방법입니다.

다음 매개변수 구문은 메타데이터와 함께 float3 매개변수를 선언합니다.

parameter float3 weights
<
     defaultValue : float3( 0.5, 0.5, 0.5 );    
     minValue :   float3( 0.1, 0.1, 0.1 );    
     maxValue :  float3( 0.9, 0.9, 0.9 );    
     description : "A three element vector of weight values."
>;


플래시 플레이어에서 매개변수 값에 접근하려면 커널을 포함하는 액션스크립트 Shader 객체의 data 속성을 사용합니다. 현재로서는, 액션스크립트에서 다음 구문으로 위 매개변수의 최솟값과 최댓값에 접근할 수 있습니다(myShader는 위 매개변수가 있는 커널을 포함하는 Shader 객체로 가정).

var currentWeights:Array = myShader.data.weights.value;
var minWeights:Array = myShader.data.weights.minimumValue;
var maxWeights:Array = myShader.data.weights.maximumValue;


매개변수가 float3 벡터 타입이기 때문에 반환되는 액션스크립트 배열은 요소가 셋일 것입니다. 매개변수가 float 같은 스칼라 타입이였다면 반환되는 배열은 요소가 하나일 것입니다.


커널 내보내고 불러오기

플래시 플레이어에서 사용하기 위해 커널을 컴파일하고 내보내려면 픽셀 벤더 툴킷에서 Export Kernel Filter for Flash Player 명령을 사용하세요. 커널은 .pbj 파일 확장자로 내보내집니다(그림 1을 보세요).

사용자 삽입 이미지
그림 1. 픽셀 벤더 툴킷에서 커널 내보내기

플래시 플레이어에서 픽셀 벤더 커널을 불러오려면 컴파일된 커널을 끼워넣거나 불러와야 합니다.

플래시 CS4 프로페셔널에서 새롭게 지원하는 Embed 태그는 액션스크립트 컴파일러에게 SWF 파일을 생성할 때 픽셀 벤더 커널을 끼워넣을 것을 명령합니다. Embed 태그는 다음 예제에 보이듯이 Class 타입의 변수 정의와 함께 사용됩니다.

[Embed(source="channelscrambler.pbj", mimeType="application/octet-stream")]
var ChannelScramblerKernel:Class;


커널을 사용하려면 그 클래스(이 경우 ChannelScramblerFilter)의 인스턴스를 생성합니다. 다음 코드는 끼워넣은 커널을 새 Shader 객체와 ShaderFilter 객체를 생성하기 위해 사용하며, ShaderFilter 객체는 Stage 객체 위의 MovieClip 인스턴스에 적용됩니다.

var camellia_mc:MovieClip;


//Embed the PixelBender kernel in the output SWF
[Embed(source="channelscrambler.pbj", mimeType="application/octet-stream")]
var ChannelScramblerKernel:Class;

var shader:Shader = new Shader(new ChannelScramblerKernel() );
var shaderFilter:ShaderFilter = new ShaderFilter( shader );
camellia_mc.filters = [ shaderFilter ];


보통 Embed 태그를 쓰는 게 픽셀 벤더 커널을 불러오는 가장 간단한 방법이지만 런타임에 커널을 불러올 수도 있습니다. 다음 예제에서는 커널을 불러오기 위해 URLLoader 클래스를 사용합니다.

var camellia_mc:MovieClip;

var urlRequest:URLRequest = new URLRequest( "channelscrambler.pbj" );
var urlLoader:URLLoader = new URLLoader();
urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
urlLoader.addEventListener( Event.COMPLETE, applyFilter );
urlLoader.load( urlRequest );

function applyFilter( event:Event ):void
{
 trace("apply");
 urlLoader.removeEventListener( Event.COMPLETE, applyFilter );
 var shader:Shader = new Shader( event.target.data );
 var shaderFilter:ShaderFilter = new ShaderFilter( shader );
 camellia_mc.filters = [ shaderFilter ];
}


Note: 플래시 CS4 프로페셔널은 Embed 태그를 사용할 때 플렉스 SDK 안의 Flex.swc 라이브러리를 사용합니다. 이 SDK는 플래시 CS4와 함께 설치되며 보통 Common/Configuration/ActionScript 3.0/libs/flex_sdk_3 하위 디렉토리에 있습니다. Embed 태그를 사용하여 무비를 처음 테스트하거나 제작할 때 여러분은 플렉스 SDK의 위치를 확인할 것을 요청받을 것입니다. 플래시 CS4와 함께 설치된 플렉스 SDK를 사용하기 위해 OK를 누를 수 있고, 다른 버전의 플렉스를 사용하길 원한다면 경로를 바꿀 수도 있습니다. 프로젝트에 사용한 설정은 나중에 Library path 탭의 Advanced ActionScript 3.0 Settings 대화 상자에서 바꿀 수 있습니다. (Publish Settings의 Flash 탭에서 Script 펼침 메뉴 옆의 Settings 버튼을 눌러서 Advanced ActionScript 3.0 Settings 대화 상자에 접근하세요.)


블렌드 사용하기

블렌드는 블렌드가 적용되는 표시 객체의 색상을 Stage 위의 그 표시 객체 밑의 색상들과 결합시킵니다. 플래시 플레이어는 몇 가지 내장 블렌드를 지원하며 BlendMode 클래스에 정의되어 있습니다. 연습으로 내장 블렌드 두 개를 따라 만들어 보고, 내장 옵션만으로는 쉽게 만들 수 없는 블렌드를 만들 것입니다.

표시 객체에 블렌드를 적용하려면 불러온 커널 바이트코드를 가진 Shader 객체를 생성하고 그 객체를 표시 객체의 blendShader 속성에 할당합니다. 블렌드 커널의 입력은 두 개여야 합니다. 첫 번째 입력은 (blendShader 속성이 설정되는) 전경 표시 객체입니다. 두 번째 입력은 전경 객체 밑의 아무거나입니다. 마스크나 질감 등을 제작하기 위해 추가 입력을 사용한다면 블렌드를 적용하기 전에 여러분이 직접 이미지를 BitmapData 객체 형식으로 입력에 할당해야 합니다.

곱하기(multiply)

곱하기 블렌드에서 전경 객체의 각 색상은 배경 객체의 색상에 의해 곱해집니다. 이 블렌드는 한 이미지가 완전히 하얀색인 경우를 제외하고 결과를 어둡게 만듭니다:

사용자 삽입 이미지

다음 커널은 foreground와 background라는 두 입력과 result라는 출력을 선언합니다. evaluatePixel() 함수 안에서, 현재 좌표에 있는 픽셀은 sampleNearest() 함수를 사용하여 각 이미지에서 추출됩니다. 그리고 픽셀들은 곱해집니다.

<languageVersion : 1.0;>

kernel MultiplyBlend
<   namespace : "com.adobe.example";
    vendor : "Adobe Systems Inc.";
    version : 1;
    description : "A simple multiply blend";
>
{
    input image4 foreground;
    input image4 background;
    output pixel4 result;

    void evaluatePixel()
    {
        pixel4 a = sampleNearest( foreground, outCoord() );
        pixel4 b = sampleNearest( background, outCoord() );
        result = a * b;
    }
}


Note: 픽셀 벤더 언어에서 두 벡터를 곱할 때, 구성 값들 중 대응하는 짝끼리 곱해집니다. 그러므로 예를 들어 a와 b가 float2 벡터라면 이 구문은
 a * b

이 두 구문과 동등합니다.
 a.x * b.x
 a.y * b.y


다음 액션스크립트 코드는 블렌드를 불러와서 적용하는데 사용됩니다.

var camellia_mc:MovieClip;

//Embed the Pixel Bender kernel in the output SWF
[Embed(source="multiplyblend.pbj", mimeType="application/octet-stream")]
var MultiplyBlendKernel:Class;

var shader:Shader = new Shader( new MultiplyBlendKernel() );
camellia_mc.blendShader = shader;


영사막(screen)

영사막 블렌드는 색상들을 반전시키고, 둘을 곱하고, 결과를 반전시킵니다. 이는 곱하기 블렌드를 적용하는 것과 다른 효과를 가집니다. 영사막 블렌드는 이미지가 완전히 검은 경우를 제외하고는 결과를 밝게 합니다.

사용자 삽입 이미지

<languageVersion : 1.0;>

kernel ScreenBlend
<   namespace : "com.adobe.example";
    vendor : "Adobe Systems Inc.";
    version : 1;
    description : "Screen blend";
>
{
    input image4 foreground;
    input image4 background;
    output pixel4 result;
                       
    void evaluatePixel()
    {
        pixel4 a = sampleNearest( background, outCoord() );
        pixel4 b = sampleNearest( foreground, outCoord() );

        result = 1.0 - (1.0 - a) * (1.0 - b);
       
    }
}


여러분이 볼 수 있듯이 이 커널은 곱하기 커널과 거의 일치합니다. 결과를 만들어 내는 데 사용한 연산만이 바뀌었습니다. 이 예제는 다른 셰이더를 불러와 적용하는 것만 빼면 같은 액션스크립트 코드를 사용합니다.

강한 조명(hard light)

강한 조명 블렌드는 곱하기 블렌드와 영사막 블렌드의 결합입니다. 전경 픽셀이 50% 회색보다 밝다면 영사막 블렌드가 수행됩니다. 그렇지 않으면 곱하기 블렌드가 수행됩니다.

사용자 삽입 이미지

<languageVersion : 1.0;>

kernel HardLightBlend
<   namespace : "com.adobe.example";
    vendor : "Adobe Systems Inc.";
    version : 1;
    description : "Hard light blend";
>
{
    input image4 foreground;
    input image4 background;
    output pixel4 result;

    void evaluatePixel()
    {
        pixel4 a = sampleNearest( background, outCoord() );
        pixel4 b = sampleNearest( foreground, outCoord() );

        float gray = (b.r + b.g + b.b)/3.0;
       
        if( gray < 0.5 )
        {
            result = 2.0 * a * b;
        }
        else
        {
            result = 1.0 - 2.0 * (1.0 - a) * (1.0 - b);
        }
    }
}


강한 조명 블렌드는 이전의 두 개보다는 조금 더 복잡합니다. 먼저 색상 채널들의 평균을 내어 전경 이미지의 픽셀의 회색 수준이 계산됩니다. 그리고 곱하기 블렌드 연산을 할지 영사막 블렌드 연산을 할지를 선택하기 위해 if 구문이 사용됩니다.

이 예제 역시 같은 액션스크립트 코드를 사용하지만 다른 셰이더를 불러와 적용합니다.

펄린 무늬(Perlin grain)

내장 블렌드로는 이루기 어려운 것을 해보죠. 다음 필터는 나뭇결 효과 또는 대리석 무늬 효과를 만들어 내기 위해 노이즈 질감과 sin() 함수를 사용합니다. 이 효과는 노이즈 질감의 특색에 의존하고 대체로 펄린 형식의 노이즈처럼 보입니다.

사용자 삽입 이미지

이 셰이더는 노이즈 이미지로부터 픽셀 값을 추출하여 작동합니다. 셰이더는 노이즈 픽셀을 이미지에 직접 사용하는 대신 sin() 함수의 연속에 노이즈 값을 공급합니다. 전경은 그 결과에 의해 곱해집니다. turbulence 매개변수는 결과 효과의 굴절도?(curviness)를 조절하는데 사용됩니다.

<languageVersion: 1.0;>

kernel GrainBlend
<   namespace : "com.adobe.example";
    vendor : "Adobe Systems Inc.";
    version : 1;
    description : "Creates a wood grain or marbling effect"; >
{
    input image4 background;   
    input image4 noise;

    output pixel4 dst;
    parameter float turbulence
    <
        maxValue : 500.0;
        minValue : 0.0;
        defaultValue : 150.0;
    >;

    void evaluatePixel()
    {  
        pixel4 a = sampleNearest(background, outCoord());
        pixel4 b = sampleNearest(noise, outCoord());
       
       
        float alpha = a.a; //save the original alpha

        if( (b.a > 0.0) && (a.a > 0.0)){
            float seed = outCoord().x + (((b.r + b.g + b.b)/3.0)  * turbulence);

            float grain = (0.7 * sin(seed) + 0.3 * sin(2.0 * seed + 0.3) + 0.2 * sin(3.0 * seed + 0.2));
            dst = sampleNearest(background, outCoord()) * (grain + 0.5);
            dst.a = alpha; //restore the original alpha          
        }
        else {
            //Just copy the background pixel outside the area of the noise image
            dst = sampleNearest(background, outCoord());
        }

    }
}


이 예제에 사용된 액션스크립트 코드는 이전의 예제들과 같은 방법으로 셰이더를 불러와 적용합니다. 추가적으로 이 예제는 turbulence 매개변수를 조절하기 위해 슬라이더를 생성합니다.

import fl.controls.Slider;
import fl.events.SliderEvent;

var noise_mc:MovieClip;
var turbulence:Slider;

//Embed the Pixel Bender kernel in the output SWF
[Embed(source="grainblend.pbj", mimeType="application/octet-stream")]
var GrainBlendKernel:Class;

//Create the Shader object
var shader:Shader = new Shader( new GrainBlendKernel() );

//Set the slider values based on the parameter metadata
turbulence.minimum = shader.data.turbulence.minValue;
turbulence.maximum = shader.data.turbulence.maxValue;
turbulence.value   = shader.data.turbulence.defaultValue;
turbulence.liveDragging = true;
turbulence.addEventListener( SliderEvent.CHANGE, updateFilter );

//Apply the blend
noise_mc.blendShader = shader;

function updateFilter( event:SliderEvent ):void
{
 shader.data.turbulence.value = [turbulence.value];
 noise_mc.blendMode = BlendMode.NORMAL;
 noise_mc.blendShader = shader;
}


이 예제는 셰이더의 turbulence 매개변수를 조절하는데 슬라이더를 사용합니다. 슬라이더의 최솟값, 최댓값, 초기값은 매개변수 메타데이터에 따라 설정됩니다. updateFilter() 메서드는 Slider 객체가 change 이벤트를 전달할 때마다 매개변수 값을 변경하는데 사용됩니다. 여러분이 표시 객체의 blendShader 속성을 설정할 때 셰이더 객체가 복제되기 때문에 원래 Shader 객체의 매개변수 값을 변경하기만 해서는 안 됩니다. 수정된 Shader 객체를 blendShader 속성에 다시 할당해야 합니다.


필터 사용하기

필터로서 사용되는 셰이더는 하나의 이미지에만 적용됩니다. Shader 객체를 생성하기 위해 블렌드에서 했던 것처럼 커널을 포함하는 Shader 객체를 넘겨서 ShaderFilter 객체를 생성해야 합니다.

var shader:Shader = new Shader( loadedBytes );
var shaderFilter:ShaderFilter = new ShaderFilter( shader );


ShaderFilter 객체는 셰이더를 "감싸고", 여러분이 표시 객체의 filters 객체에 ShaderFilter 객체를 추가함으로써 내장 필터처럼 사용할 수 있게 합니다.

displayObject.filters = [ shaderFilter ];

셰이더가 적용되는 객체는 자동으로 커널의 첫번째 입력으로 설정됩니다. 필터 커널이 추가적인 이미지를 입력으로서 받아들인다면 표시 객체에 필터를 할당하기 전에 설정해야 합니다.

이미 ChannelScrambler라는 간단한 필터를 봤으니 더 복잡한 예제인 가우시안 흐림으로 곧장 갑시다.

가우시안 흐림(Gaussian blur)

가우시안 흐림은 회선 필터의 한 종류입니다. (회선convolution은 근처 픽셀들의 가중평균을 계산하는 필터를 화려하게 일컫는 이름입니다.) 플래시 CS4 전문가용에 임의 크기의 회선 필터를 제작하는 클래스가 내장되었지만, 픽셀 벤더로 가우시안 흐림을 프로그래밍하는 것은 픽셀 벤더의 몇 가지 중요한 면을 설명하는 좋은 연습입니다.

사용자 삽입 이미지

플래시 플레이어는 커널 코드에서의 루프를 지원하지 않기 때문에 범용 회선 필터는 픽셀 벤더로 구현하기 어렵습니다. 우리는 for 루프를 쓰는 대신 이웃한 각 픽셀을 추출하기 위해 개개의 프로그램 구문을 작성해야 합니다. 이 예제 커널은 1에서 6 사이(각각 3 x 3에서 13 x 13 크기의 회선 행렬과 일치)의 반지름을 추출할 수 있는 Gaussian blur를 생산합니다.

이 필터는 가우시안 흐림을 분리하며, 이는 연산을 두 과정을 거쳐 수행할 수 있음을 뜻합니다. 한 과정에서 이미지를 수평으로 흐리게 하고, 다른 과정에서 이미지를 수직으로 흐리게 합니다. 이는 연산을 어느 정도 줄이는데, 각각의 최종 픽셀에 대해 반지름의 4배쯤 되는 수의 픽셀만 추출하여 가중 평균을 내기 때문입니다. 반지름2 의 2배 되는 수의 픽셀을 추출하는 대신이요. 예를 들어 이 필터가 지원하는 가장 큰 반지름의 경우, 각 최종 출력 픽셀을 위해 입력 픽셀이 수직으로 26개, 수평으로 26개 추출되어 결합됩니다. 필터가 블러를 한 번에 계산한다면 각 출력 픽셀에 대해 입력 픽셀을 169개 추출해야 할 것입니다.
The filter takes advantage of the fact that a Gaussian blur is separable, which simply means that you can perform the operation in two passes. One pass blurs the image horizontally, and the other pass blurs the image vertically. This saves several calculations, since for each final pixel, only about 4 times the radius pixels have to be sampled, and weighted and averaged, rather than 2 times the radius2 pixels. For example, at the largest radius supported by this filter, 26 input pixels are sampled for each final output pixel in both the vertical and horizontal passes combined. If the filter computed the blur in a single pass, 169 input pixels would have to be sampled for each output pixel.

for 루프가 없기 때문에, 커널은 각 정수 반지름 값을 따로 처리합니다. 허락된 각 반지름 값에 대해, 현재 픽셀에서 양옆으로 반지름만큼 떨어진 두 픽셀이 추출됩니다. 가우시안 가중은 양쪽 픽셀에 대해 같으므로 같이 더해집니다. 필요한 모든 픽셀이 추출되면 가중 인자와 비례 인자가 적용됩니다.
To overcome the lack of a for loop, the kernel treats each integer radius value separately. For each allowed radius value, the two pixels located at that distance to either side of the current pixel are sampled. The Gaussian weights are the same for both pixels, so they are added together. Once all the necessary pixels are sampled, the weight and scale factors are applied.

다음 커널 코드는 수평 방향에 사용됩니다. 비슷한 코드가 수직 방향에 사용됩니다.

<languageVersion: 1.0;>

kernel HorizontalGaussianBlur
<   namespace : "com.adobe.example";
    vendor : "Adobe Systems Inc.";
    version : 1;
    description : "The horizontal convolution of a Gaussian blur"; >
{
    input image4 src;
    output float4 result;
   
    parameter int radius
    <
        minValue : 1;
        maxValue : 6;
        defaultValue : 6;
    >;
   
    void evaluatePixel()
    {
       pixel4 center, band1, band2, band3, band4, band5, band6;
       float2 pos = outCoord();
      
       //Sample image in bands
       if( radius > 5 )
       {
            band4 = sampleNearest(src, float2(pos.x - 6.0, pos.y))
                  + sampleNearest(src, float2(pos.x + 6.0, pos.y));
       }
       if( radius > 4 )
       {
            band4 = sampleNearest(src, float2(pos.x - 5.0, pos.y))
                  + sampleNearest(src, float2(pos.x + 5.0, pos.y));
       }       
       if( radius > 3 )
       {
            band4 = sampleNearest(src, float2(pos.x - 4.0, pos.y))
                  + sampleNearest(src, float2(pos.x + 4.0, pos.y));
       }      
       if( radius > 2 )
       {
            band3 = sampleNearest(src, float2(pos.x - 3.0, pos.y))
                  + sampleNearest(src, float2(pos.x + 3.0, pos.y));
       }
       if( radius > 1 )
       {
            band2 = sampleNearest(src, float2(pos.x - 2.0, pos.y))
                  + sampleNearest(src, float2(pos.x + 2.0, pos.y));
       }

       band1 = sampleNearest(src, float2(pos.x - 1.0, pos.y))
             + sampleNearest(src, float2(pos.x + 1.0, pos.y));
       center = sampleNearest(src, pos);
      
       //Apply weights and compute resulting pixel
       if( radius == 6 )
       {
            result = (band6 + (band5 * 12.0) + (band4 * 66.0) + (band3 * 220.0) + (band2 * 495.0) + (band1 * 792.0) + (center * 924.0))/4096.0;
       }
       if( radius == 5 )
       {
            result = (band5 + (band4 * 10.0) + (band3 * 45.0) + (band2 * 120.0) + (band1 * 210.0) + (center * 252.0))/1024.0;
       }      
       if( radius == 4 )
       {
            result = (band4 + (band3 * 8.0) + (band2 * 28.0) + (band1 * 56.0) + (center * 70.0))/256.0;
       }
       if( radius == 3 )
       {
            result = (band3 + (band2 * 6.0) + (band1 * 15.0) + (center * 20.0))/64.0;
       }
       if( radius == 2 )
       {
            result = (band2 + (band1 * 4.0) + (center * 6.0))/16.0;
       }
       if( radius == 1 )
       {
            result = (band1 + (center * 2.0))/4.0;
       }
    }
}


완전한 효과를 위해 두 커널 모두 필터로서 적용해야 합니다. 적용 순서는 상관 없습니다. 밑에 보이는 액션스크립트 코드는 반지름 매개변수를 제어하기 위해 슬라이더를 사용합니다.

import fl.controls.Slider;
import fl.events.SliderEvent;

var camellia_mc:MovieClip;
var radiusSlider:Slider;

//Embed the Pixel Bender kernel in the output SWF
[Embed(source="verticalgaussianblur.pbj", mimeType="application/octet-stream")]
var VerticalBlurKernel:Class;
[Embed(source="horizontalgaussianblur.pbj", mimeType="application/octet-stream")]
var HorizontalBlurKernel:Class;

//Create the shaders
var vBlurShader:Shader = new Shader( new VerticalBlurKernel() );
var hBlurShader:Shader = new Shader( new HorizontalBlurKernel() );

//Create the filters
var hBlurFilter:ShaderFilter = new ShaderFilter( hBlurShader );
var vBlurFilter:ShaderFilter = new ShaderFilter( vBlurShader );

//Initialize the slider using the radius parameter metadata
radiusSlider.value = hBlurShader.data.radius.value;
radiusSlider.minimum = hBlurShader.data.radius.minValue;
radiusSlider.maximum = hBlurShader.data.radius.maxValue;
radiusSlider.liveDragging = true;
radiusSlider.addEventListener( SliderEvent.CHANGE, updateFilters );

//Apply the filters
camellia_mc.filters = [ hBlurFilter, vBlurFilter ];

//Reapply the filters when the slider is changed
function updateFilters( event:SliderEvent ):void
{
 hBlurShader.data.radius.value = vBlurShader.data.radius.value = [radiusSlider.value];
 camellia_mc.filters = [ hBlurFilter, vBlurFilter ];
}


이 예제는 각 필터의 반지름을 같은 값으로 설정합니다. 블렌드의 경우와 같이, 매개변수 값을 바꾼 후에는 셰이더 객체를 표시 객체에 다시 할당해야 합니다.


칠 사용하기

커널을 면적 칠로서 사용하려면 커널 바이트코드를 포함하는 Shader 객체를 생성하고, 뭔가를 그릴 때 표시 객체의 graphics 속성의 beginShaderFill() 함수에 넘깁니다. 비트맵 칠과 마찬가지로 셰이더 칠은 표시 객체의 원점에 등록됩니다. 이 등록은 변환 행렬을 사용하여 수정할 수 있습니다.

면적 또는 선 칠로서 사용되는 셰이더는 입력 이미지에 자동으로 할당되지 않습니다. (이미지가 칠하기 알고리즘의 일부로서 필요하다면 beginShaderFill() 메서드를 호출하기 전에 입력에 이미지를 명시적으로 할당해야 합니다.)

바둑판

다음의 칠 예제는 바둑판 무늬를 만듭니다. 바둑판 사각형들의 크기와 색상은 커널 매개변수에 의해 조절됩니다.

사용자 삽입 이미지

바둑판 알고리즘은 현재 픽셀 위치를 바둑판 크기의 배수와 비교하기 위해 나머지(modulo) 함수를 사용합니다.

float vertical = mod(position.x, checkerSize * 2.0);

나머지 함수는 x 좌표를 checkSize의 2배로 나눈 나머지를 반환합니다. 예를 들어 checkSize가 10이면 x가 증가할 때 0-10, 0-10, 0-19, ... 형식을 얻습니다. 그러므로 결과가 바둑판 크기보다 작을 경우 커널은 A 색상을 그리고, 그렇지 않으면 B 색상을 그립니다. 이는 줄무늬를 만듭니다. 바둑판 무늬를 만들려면 이렇게 이 기법을 수평 방향과 수직 방향 둘 다에 적용해야 합니다.

float vertical   = mod(position.x, checkerSize * 2.0);
float horizontal = mod(position.y, checkerSize * 2.0);


여전히 남은 질문은 이 결과들을 어떻게 묶느냐입니다. 한 입력만 true이면 true를 반환하는 논리 XOR 연산자 (^^)가 여기에 최적입니다.

( vertical < checkerSize ) ^^ ( horizontal < checkerSize )

다음 예제는 완전한 커널 코드를 보여줍니다.

<languageVersion : 1.0;>

kernel CheckerFill
<   namespace : "com.adobe.example";
    vendor : "Adobe Systems Inc.";
    version : 1;
    description : "A checkered field generator";
>
{
    output pixel4 dst;
   
    parameter float checkerSize
    <
        defaultValue : 10.0;
        minValue : 1.0;
        maxValue : 75.0;
    >;
   
    parameter pixel4 colorA
    <
        defaultValue : pixel4(0.0, 1.0, 1.0, 1.0);
    >;
   
    parameter pixel4 colorB
    <
        defaultValue : pixel4( 0.0, 0.0, 0.0, 1.0 );
    >;
   
    void evaluatePixel()
    {       
        float2 position = outCoord();
        float vertical   = mod(position.x, checkerSize * 2.0);
        float horizontal = mod(position.y, checkerSize * 2.0);
        dst = (( vertical < checkerSize ) ^^ ( horizontal < checkerSize )) ? colorA : colorB;
    }
}


이 예제에 사용한 액션스크립트 코드는 커널에 더 많은 매개변수들이 사용되고 매개변수들 자체가 좀 더 복잡하기 때문에 이전 예제들보다 조금 복잡합니다.

예제에서 initialize() 함수는 CheckFill 커널 불러오기가 끝나면 호출됩니다. 이 함수는 Shader 객체를 생성하고, 컨트롤의 초기 값을 설정하기 위해 커널 매개변수들의 메타데이터를 사용합니다. 그리고 함수는 셰이더 칠을 사용하여 원을 그리는 drawShape() 함수를 호출합니다.

칠을 갱신하기 위해 컨트롤 중 하나가 change 이벤트를 전달할 때마다 drawShape() 함수가 호출됩니다. 이 함수는 현재 컨트롤 값에 기반해 커널 매개변수들을 설정하고, 현재 그래픽을 지우고, 도형을 다시 그립니다.

import fl.controls.Slider;
import fl.events.SliderEvent;
import fl.controls.ColorPicker;
import fl.events.ColorPickerEvent;

var filledShape:Shape = new Shape();
var checkerSize:Slider;
var colorA:ColorPicker;
var colorB:ColorPicker;
var areaShader:Shader;

//Embed the Pixel Bender kernel in the output SWF
[Embed(source="checkerfill.pbj", mimeType="application/octet-stream")]
var CheckerFillKernel:Class;

//Create the Shader object using the embedded kernel
areaShader = new Shader( new CheckerFillKernel() );

//Set controls using shader parameter metadata
checkerSize.minimum = areaShader.data.checkerSize.minValue;
checkerSize.maximum = areaShader.data.checkerSize.maxValue;
checkerSize.value   = areaShader.data.checkerSize.value;
checkerSize.liveDragging = true;
checkerSize.addEventListener( SliderEvent.CHANGE, updateFilters );

colorA.selectedColor = vectorToColor( areaShader.data.colorA.value );
colorA.addEventListener( ColorPickerEvent.CHANGE, updateFilters );

colorB.selectedColor = vectorToColor( areaShader.data.colorB.value );
colorB.addEventListener( ColorPickerEvent.CHANGE, updateFilters );

filledShape.x = 150;
drawShape();
addChild( filledShape );

function updateFilters( event:Event ):void
{
 areaShader.data.checkerSize.value = [ checkerSize.value ];
 areaShader.data.colorA.value = colorToVector( colorA.selectedColor | 0xff000000 );
 areaShader.data.colorB.value = colorToVector( colorB.selectedColor | 0xff000000 );
 drawShape();
}

function drawShape():void
{
 with( filledShape.graphics )
 {
  clear();
  beginShaderFill( areaShader );
  drawCircle( 100, 75, 75 );
  endFill();
 }
}

function vectorToColor( pixelChannels:Array ):uint
{
 return (pixelChannels[3] * 0xff << 24) |
     (pixelChannels[0] * 0xff << 16) |
     (pixelChannels[1] * 0xff << 8)  |
      pixelChannels[2] * 0xff;
}

function colorToVector( color:uint ):Array
{
 var result:Array = new Array(4);

 result[3] = ((color >> 24) & 0x000000ff)/0xff;
 result[0] = ((color >> 16) & 0x000000ff)/0xff;
 result[1] = ((color >> 8)  & 0x000000ff)/0xff;
 result[2] =  (color        & 0x000000ff)/0xff;

 return result;
}


위 예제에는 커널 매개변수를 설정하기 위해 슬라이더를 넣었습니다. 이 예제에 사용한 색상 선택 컨트롤은 간단하지 않습니다. 매개변수 값들은 플래시 플레이어에서 배열로서 접근됩니다. checkSize 같은 스칼라 매개변수의 경우 배열은 한 값만을 가집니다. 색상들 같은 벡터 타입을 위해 배열은 벡터의 각 요소를 포함합니다. colorA와 colorB 매개변수가 pixel4 타입이기 때문에 배열에는 각각 픽셀의 한 채널인 네 값이 있습니다. 한편 액션스크립트에서 색상은 모든 채널 정보를 포함하는 하나의 32 비트 uint 값으로 표현됩니다. 게다가 알파 채널은 uint 색상의 첫번째 채널이지만 Shader 객체의 매개변수 배열에선 마지막 채널입니다. 예제의 함수 vectorToColor()colorToVector()는 ColorPicker 객체들과 셰이더 매개변수들 사이에 선택 색상을 보낼 수 있게 하기 위해 색상의 형식을 변환합니다.

vectorToColor() 함수는 픽셀 벤더 커널이 반환하는 각 색상 채널에 256(16진수 값 0xff)을 곱합니다. 그리고 결과 값은 비트 왼쪽 쉬프트 연산자(<<)를 사용하여 상응하는 argb 위치로 옮겨집니다. 마지막으로 네 채널은 비트 OR 연산자(|)에 의해 하나의 uint로 묶이고 반환됩니다.

colorToVector() 함수는 반대 연산을 수행합니다. 각 채널에 대해 함수는 비트 오른쪽 쉬프트 연산자(>>)를 쓰고 비트 AND 연산자(&)로 각 채널의 비트를 걸러내고 값을 0에서 1 사이로 맞추기 위해 256(0xff)으로 나눕니다. 그 결과는 픽셀 벤더가 기대하는 순으로 배열의 알맞은 요소에 할당된 다음 반환됩니다.

Posted by codeonwort
,