Chapter 3. Skin in the "Dawn" Demo
원문: http://http.developer.nvidia.com/GPUGems/gpugems_ch03.html
Chapter 3. Skin in the "Dawn" Demo
Curtis Beeson
NVIDIA
Kevin Bjorke
NVIDIA
3.1 Introduction
"Dawn" is a demonstration that was created by NVIDIA to introduce the GeForce FX product line and illustrate how programmable shading could be used to create a realistic human character, as shown in Figure 3-1. The vertex shaders (described in Chapter 4, "Animation in the 'Dawn' Demo") deform a high-resolution mesh through indexed skinning and blend shapes, and they provide setup for the lighting model used in the fragment shaders. The skin and wing fragment shaders produce both range and detail that could not have been achieved before the introduction of programmable graphics hardware. [1]
“Dawn”은 GeForce FX 제품 라인을 소개하고 그림 3-1과 같이 프로그래밍 가능한 쉐이딩이 실제 같은 인간 캐릭터를 생성하는데 어떻게 사용될 수 있는지 설명하기 위해 NVIDIA에서 만든 데모이다. (챕터 4 "Animation in the 'Dawn' Demo"에 설명되어 있는) 정점 쉐이더는 색인된 스키닝과 형태 혼합을 통해 고해상도 메쉬를 변형하고 단편 쉐이더에서 사용하는 조명 모델을 위한 설정을 제공한다. 피부와 날개 단편 쉐이더는 프로그래밍 가능한 그래픽 하드웨어가 생기기 전에는 이루어질 수 없었던 범위와 세밀함을 모두 제공한다.
This chapter discusses how programmable graphics hardware was used to light and accelerate the skin shader on the Dawn character in the demo.
이 챕터는 프로그래밍 가능한 그래픽 하드웨어가 데모에 있는 Dawn 캐릭터에 조명을 주고 피부 쉐이더를 가속하는데 어떻게 사용되었는지를 논의한다.
3.2 Skin Shading
Skin is a difficult surface to simulate in computer graphics for a variety of reasons. Even the high-end production graphics used in movies are unable to simulate a photorealistic human character that can withstand close examination. Humans gain a great deal of information with nonverbal cues: the shifting of weight, the particular way of walking, facial expressions, and even the blush of someone's skin. Researchers have shown that babies recognize and gaze at facial expressions within moments of birth. We humans are therefore very perceptive when it comes to detecting irregularities in the way characters move and emote, and in the subtle coloring of their skin. Although few people may understand terms such as subsurface scattering and rim lighting, almost anybody can tell you when your rendering got them wrong.
피부는 여러 가지 이유로 컴퓨터 그래픽으로 시뮬레이션하기에 어려운 표면이다. 영화에서 사용되는 하이엔드 그래픽 제품일지라도 실사 같은 인간 캐릭터를 시뮬레이션할 수 없다. 인간은 무게의 이동, 특이한 걸음걸이, 얼굴 표정, 피부의 홍조 등의 비언어적인 신호에서 많은 정보를 얻는다. 연구원들인 아기들이 태어나는 순간에 얼굴 표정을 응시하고 인지한다는 것을 보여주고 있다. 그러므로 우리 인간은 캐릭터의 움직이고 감정을 나타내는 방식과 피부의 미묘한 색에서의 불규칙적인 변화를 감지하는데에 매우 예민하다. 비록 소수의 사람만이 표면내부 산란(subsurface scattering)과 가장자리 조명(rim lighting) 같은 말들을 이해할지도 모르지만, 그것이 잘못 렌더링되었다는 것은 누구든지 알 수 있다.
In addition to shading issues, people may be quick to say that a skin shader looks like plastic because of the way the skin moves, so it is important to address the problems of character setup. Chapter 4 describes those techniques used in the "Dawn" demo for driving the body using accumulated indexed skinning, and for adding emotion to Dawn's face through blend targets. These techniques provide a passable approximation for Dawn and for the actions she needs to perform, but they might fail to fully describe the way skin slides, flexes, and sags over a real skeleton for other purposes. When creating a character, it's important to model both the rigging and the shading appropriately for the range of views that the observer will actually see.
쉐이딩 이슈와 더불어, 사람들은 피부가 움직이는 방식 때문에 피부 쉐이더가 플리스틱처럼 보인다고 말할지도 모른다. 따라서 캐릭터 설정의 문제들을 다루는 것이 중요하다. 챕터 4는 누적 색인된 스키닝을 사용하여 몸을 움직이고 타겟 블렌딩을 통해 Dawn의 얼굴에 감정을 넣기 위해 “Dawn” 데모에 사용된 그러한 기법을 설명하고 있다. 이 기법들은 Dawn과, 그녀가 실행할 필요가 있는 행동들을 위한 쓸만한 근사를 제공한다. 하지만 그 기법들이 다른 목적을 위해 실제 뼈대 위에서 피부가 미끄러지고, 움직이고, 처지는 방식을 완전히 나타내지는 못할 것이다. 캐릭터를 만들 때, 관찰자가 실제로 보는 시야 범위에서 적절히 리깅(rigging)과 쉐이딩을 모두 모델링하는 것이 중요하다.
3.3 Lighting the Scene
Once the skin is moving in as realistic a manner as possible, you can focus on the shading. The most straightforward way to break down the shading of a surface is to examine the lighting environment of the scene and then look at the skin's response to that light.
일단 피부가 가능한한 실제와 같은 방식으로 움직이고 있으면, 쉐이딩에 집중할 수 있다. 표면의 쉐이딩을 분석하는 가장 직접적인 방식은 장면의 조명 환경을 검사한 다음, 그 빛에 대한 피부의 반응을 보는 것이다.
3.3.1 A High-Dynamic-Range Environment
We wanted to create our character and place her in a setting that defied the sort of flat, simple-looking shading that typifies traditional real-time graphics techniques. Older graphics architectures were built around the concepts of Gouraud and Phong shading, which describe all incoming light as radiating from infinitely small point, directional, and spot lights. The diffuse and specular responses of material surfaces were usually described using textures and equations that failed to describe a suitable range of light response.
우리는 우리의 캐릭터를 만들어서 전통적인 실시간 그래픽 기법을 대표하는 평평하고 단순해 보이는 종류의 쉐이딩을 허용하지 않은 환경에 그녀를 놓기를 원했다. 이전의 그래픽 아키텍쳐는 모든 입사 조명을 무한히 작은 점 광원, 지향성 광원, 스포트라이트 광원에서 나오는 것으로 설명하는 Gouraud와 Phong 쉐이딩의 개념을 중심으로 만들어졌다. 물질 표면의 난반사와 반사 반응은 보통 적절한 범위의 빛 반응을 나타내지 못하는 텍스쳐와 방정식을 사용하여 나타내었다.
One undesirable result of the previous approach is that scenes tended to lack the brightness range seen in the real world. A white piece of paper on the floor would be as bright as a white light source above; characters in complex environments often looked out of place because they were lit by discrete points instead of by their surroundings.
이전 접근법에서 못마땅한 것 중 하나는 장면의 밝기 범위가 실 세계에서 보여지는 것보다 부족한 경향이 있다는 것이다. 바닥에 있는 하얀 종이는 흰색 광원 위에 있을 때와 밝기가 차이가 없을 것이다; 복잡한 환경에 있는 캐릭터는 주위 환경이 아니라 개별적인 점에서 조명을 받기 때문에 때론 어색하게 보인다.
Paul Debevec has done a great deal of research in the area of high-dynamic-range image processing and manipulation to address exactly this problem. His work can be found in a variety of SIGGRAPH publications and is made more tangible through examples in his rendered films. [2] We sought to take advantage of the new programmable real-time graphics architectures to apply these concepts to a real-time character. In particular, we wished to have Dawn lit by the environment and to allow the bright spots to cause light to bleed and soften her silhouette edges.
Paul Debevec은 정확히 이런 문제에 초점을 맞춘 HDR 이미지 처리와 조작의 분야에 많은 연구를 했다. 그의 작업은 여러 SIGGRAPH 출판물에서 발견할 수 있고 그의 렌더링된 영화에 있는 예를 통해 더 구체화되었다. 우리는 이 개념을 실시간 캐릭터에 적용하기 위해 새로운 프로그래밍 가능한 실시간 그래픽 아키텍쳐를 이용하기로 했다. 특히, 우리는 Dawn이 주위 환경으로부터 빛을 받고 밝은 조명이 그녀의 실루엣 모서리를 부드럽고 번지게 밝히기를 바랬다.
Dawn being a fairy, we found a suitable woodsy area and took a number of calibrated pictures. We used an iPIX kit that included a tripod that locks at opposite angles, a digital camera, and a 183-degree fish-eye lens. In this case, the two hemispherical pictures were taken across several shutter speeds using one of the camera's built-in macros. This collection of images provides better information about the light intensities at each pixel, as opposed to the single pixel color produced at any individual shutter speed.
Dawn을 요정으로 만들기 위해, 우리는 적당한 숲 지역을 찾았고 많은 사진을 측량하여 찍었다. 우리는 맞각에 고정된 삼각대, 디지털 카메라, 183도 어안 렌즈가 포하된 iPIX 도구를 사용했다. 이 경우에, 카메라의 내장 매크로 중 하나를 사용해 여러 셔터 스피드로 2장의 반구 사진을 찍었다. 이 사진들은 한 셔터 스피드에서 생긴 하나의 픽셀 색에 반해, 각 픽셀에서 빛의 세기에 대한 더 많은 정보를 제공한다.
We used the iPIX software to stitch the corresponding hemispheres into a single panorama, and then we used Debevec's HDRShop software to create a high-dynamic-range panorama that encodes the light color and intensity of the environment at every given angle. See Figure 3-2. HDRShop can then create a diffuse lookup map that performs a convolution to create a diffuse or specular map for a surface with a given surface roughness (which in the past would have been modeled using a Phong exponent).
우리는 상응하는 반구 사진을 하나의 파노라마에 맞추기 위해 iPIX 소프트웨어를 사용했다. 그런 다음 모든 주어진 각도에서 환경에 있는 빛의 색과 세기를 인코딩한 HDR 파노라마를 만들기 위해 Debevec의 HDRShop 소프트웨어를 사용했다. 그림 3-2를 보라. 또한, HDRShop은 주어진 표면 거칠기로 표면에 대한 난반사나 정반사 맵을 만들기 위해 회선(convolution)을 수행하는 난반사 룩업 맵을 만들 수 있다 (이전에는 퐁 지수를 사용하여 모델링했었다).
The diffuse map is a cube map, as shown in Figure 3-3, indexed using the surface normal of the mesh, and it stores a cosine-weighted average of the incoming light for that direction. Ignoring for now the fact that parts of the hemisphere may be occluded at a given point, this weighted average gives the appropriate value for computing diffuse reflection for points on the surface.
난반사 맵은 그림 3-3과 같이 메쉬의 표면 법선을 사용하여 색인되는 입방체 맵이고, 입사광을 그 방향에 대해 코사인 가중치된 평균을 구하여 저장한다. 일단은 주어진 점에서 반구의 일부분이 차단될 수도 있다는 사실은 무시하면, 이 가중된 평균 값을 통해 표면에 있는 점에 대한 난반사를 계산하는데 필요한 적당한 값을 얻을 수 있다.
The specular map is also a cube map, as shown in Figure 3-4, and is indexed using the reflection vector (the way "usual" cube maps are used for reflections). This specular map is blurred based on the roughness factor to simulate the changing surface normal at any given point on the surface.
정반사 맵 또한 그림 3-4와 같이 반사 벡터를 사용해 색인되는( “보통의” 입방체 맵이 반사를 위해 사용되는 방식) 입방체 맵이다. 이 정반사 맵은 표면에 있는 어떤 주어진 점에서 표면 법선의 변화를 시뮬레이션하기 위한 거칠기 인수를 기반으로 블러링된다.

Figure 3-4 The Specular Cube Environment Map
3.3.2 The Occlusion Term
One problem with using environment maps to describe an environment or a lighting solution is that the mapped samples are really accurate for only a single point in space. The first side effect of this is that if the fairy walks around, the background appears to be "infinitely" far away. This was a suitable approximation for us, because the fairy is small and the background should look far away in this instance. [3]
환경이나 조명을 나타내기 위해 환경맵을 사용하는 것의 한가지 문제점은 맵핑되는 샘플들이 공간에 있는 하나의 점에서만 진짜 정확하다는 것이다. 이것의 첫번째 부작용은 요정이 주위를 걸어다니면 배경이 “무한히” 멀리 있는 것으로 보인다는 것이다. 이것은 우리에겐 적절한 것이다. 왜냐하면 요정은 작고 이 경우에 배경은 멀리 보여야 하기 때문이다.
The second side effect is that the diffuse and specular maps describe the incident light from the environment that was photographed, but they do nothing to describe the shadows introduced by simulated elements such as Dawn herself. Thus, it was necessary to develop a technique to incorporate this information.
두번째 부작용은 난반사와 정반사 맵이 사진을 찍은 환경에서 있는 입사광은 나타내지만 Dawn 자신 같은 시뮬레이션된 요소에 의해 생기는 그림자는 나타내기 위해서는 아무 것도 하지 않는 다는 것이다. 그러므로, 이런 정보를 통합하는 기법을 개발할 필요가 있었다.
Many real-time shadowing techniques produce hard shadows, and we wanted a particularly soft look. Thus, we generated an occlusion term that approximated what percentage of the hemisphere above each vertex was obstructed by other objects in the scene. [4]
많은 실시간 그림자 기법들은 딱딱한 그림자를 만들어낸다. 우리는 매우 부드러워 보이는 그림자를 원했다. 그래서, 우리는 각 점 위에 있는 반구가 장면에 있는 다른 물체에 의해 몇 %나 가려지는지를 근사하는 폐색항을 만들었다.
This was done using a custom software tool that casts rays stochastically over that vertex's visible hemisphere and found collisions with other geometry. We used this technique in the "Dawn" demo and in the "Ogre" demo (content courtesy of Spellcraft Studio), though the results were then used differently. For Dawn, the occlusion term was kept constant for the vertices of her figure; her face used a blend of occlusion terms from the various morph targets.
정점의 반구 위로 확률적으로 광선을 쏘아 다른 기하와 충돌하는지를 찾아내는 커스텀 툴을 사용해서 이것을 수행했다. 우리는 이 기법을 “Dawn” 데모와 Spellcraft 스튜디오의 “Orge” 데모에 사용했다. 결과가 다르게 사용됐긴 했지만. Dawn에서 폐색항은 정점에 대해 일정하게 유지되었다; 그녀의 얼굴은 여러 모프(morph) 타겟의 폐색항을 혼합하여 사용했다.
Having reached the limits of what we could do on a prerelease GeForce FX 5800 (Dawn was created to be a launch demo for that product), we stopped shy of animating the occlusion term for every vertex on every frame, as we did for the Ogre character in Spellcraft Studio's "Yeah! The Movie" demo.
사전 공개된 GeForce FX 5800(Dawn은 이 제품의 출시 데모로 만들어졌다)에서 수행하는데에 한계에 도달하여, 우리는 매프레임마다 모든 정점에 폐색항을 애니메이션하는 것은 보류했다. Spellcraft 스튜디오의 “Yeah! The Movie” 데모에 있는 Orge 캐릭터도 마찬가지다.
3.4 How Skin Responds to Light
Skin is unlike most surfaces that we model in computer graphics because it is composed of layers of semitranslucent epidermis, dermis, and subcutaneous tissue. Subsurface scattering is the term we give to the phenomenon whereby light enters the surface, scatters through the medium, and exits at another point. This effect can commonly be seen as an orange glow of light passing through the skin when you hold your hand up in front of the Sun. This scattering under the skin is important to skin's appearance at all angles and helps give it a soft, highly distinctive character. Unfortunately, this reality defies a common assumption in graphics APIs and architectures: namely, light at one point on an object doesn't affect reflection at other points.
피부는 컴퓨터 그래픽으로 모델링하는 대부분의 표면들과 다르다. 피부는 반투명한 외피, 진피, 피하 조직의 층으로 이루어져 있기 때문이다. 표면내부 산란은 빛이 표면을 들어와서 안쪽에서 산란되고 다른 지점으로 빠져남으로 인해 생기는 현상에 주는 항이다. 이런 효과는 보통 태양 앞에 손을 올리고 있을 때 피부를 통해 통과하는 빛이 오렌지색으로 빛나는 것과 같은 현상에서 볼 수 있다. 이런 피부 아래에서의 산란은 모든 각에서 피부의 외형에 중요하고, 부드럽고 매우 특색있는 캐릭터를 만드는데 도움이 된다. 불행히도, 이런 사실은 그래픽 API와 아키텍쳐에서의 일반적인 가정, 즉 어떤 물체에 있는 한 점에서의 빛은 다른 점에서의 반사에 영향을 미치지 않는다는 가정과는 맞지 않다.
In the past, some groups have tried to emulate skin's complexity using multiple, layered texture maps. In general this approach has proven to be difficult to manage and difficult for the texture artists to work with as they previsualize the final blended color. Instead, we used a single color map, with color variations added through the shading, as shown in Figure 3-5.
이전에, 몇몇 그룹이 여러 개의 레이어된 텍스쳐 맵을 사용해서 피부의 복잡성을 흉내내려는 시도를 했다. 일반적으로 이런 접근법은 관리하기가 어렵고 텍스쳐 아티스트들이 최종적으로 혼합된 색을 미리 보면서 작업하기가 어렵다는 것을 증명한다. 그 대신에 우리는 그림 3-5와 같이 쉐이딩을 통해 변형된 색이 더해진 하나의 색 맵을 사용했다.
Furthermore, skin has extremely fine variations that affect its reflective properties. These have subtle effects on skin's appearance, particularly when the light is directly opposite the camera position—that is, edge and rim lighting. Real skin has tiny features such as vellus hairs and pores that catch the light. These details were too small for us to model explicitly, but we wanted a surface that still gave us an appropriate overall look. Adding a bump map provided some additional detail when seen in close-up—particularly for small wrinkles—but we wanted a soft appearance, not shiny, stippled plastic, and we wanted the effects to be visible regardless of the size on screen (bump maps are usually visible only when seen up close).
더욱이, 피부는 반사 속성에 영향을 주는 매우 미세한 변화가 있다. 이것은 특히 빛이 카메라의 바로 반대 위치에 있을 때, 즉 모서리와 가장자리 조명일 때 피부의 외형에 미묘한 효과를 준다. 실제 피부는 빛을 받는 솜털과 모공 같은 작은 부분들이 있다. 이런 세부사항들은 우리가 뚜렷하게 모델링하기에는 너무 작다. 하지만 우리는 표면이 여전히 전체적으로 적절한 모양이 되길 원했다. 범프맵을 추가하면 가까이서 봤을 때 다소의 추가적인 세밀함 —특히 잔주름— 을 만들 수 있었지만, 우리는 빛나고 점각된 플라스틱이 아닌 부드러운 외형을 원했고, 스크린의 크기에 상관없이 효과들이 보이기를 원했다 (범프맵은 보통 가까이서 봤을 때만 보인다).
We approximated both of these shading attributes by recognizing that we could model them as simple formulas based on the surface normal and the lighting or viewing vectors. In particular, along silhouette edges we sampled the lighting from behind Dawn, as indexed by the view vector—mixing the light coming "through" Dawn with her base skin tones to create the illusion of subsurface and edge-effect lighting, particularly for very bright areas in the background map. See Figure 3-6.
우리는 표면 법선과 조명이나 시야 벡터에 대한 간단한 공식으로 이러한 두 쉐이딩 속성들을 모델링할 수 있다는 사실을 통해 두 쉐이딩 속성을 근사했다. 특히, 시야 벡터로 색인하여, 실루엣 모서리를 따라 Dawn의 뒤에서 오는 조명을 샘플링했다. 표면내부와 모서리-효과 조명의 환영을 만들기 위해, 특히 배경맵이 매우 밝은 지역을 위해 Dawn을 “통과해서” 오는 빛을 기본 피부 톤과 혼합하였다.

Figure 3-6 The Tangent-Space Normal Map (Bump Map) for the Front of Dawn's Head
3.5 Implementation
The processes of lighting are split between the vertex and the fragment shaders. This is a one-pass lighting solution: no additional render passes or alpha blending is required to create the skin surface.
조명 처리 과정은 정점 쉐이더와 단편 쉐이더로 나뉜다. 이것은 단일 패스 조명 해법이다. 피부 표면을 만들기 위해 추가적인 렌더링 패스나 알파 블렌딩이 필요하지 않다.
3.5.1 The Vertex Shader
The primary functions of the vertex shader are to transform the coordinate into projection space and to perform those mathematical operations that are prohibitively expensive to compute in the fragment shader. As mentioned in Section 3.3.2, the vertex shader in the "Dawn" demo first applied morph targets (if any), and then skinned the mesh of over 180,000 vertices with a skeleton of 98 bones. See Chapter 4 for more.
정점 쉐이더의 주요 기능은 투영 공간으로 좌표를 변환하고 단편 쉐이더에서 계산하기에는 비용이 많이 드는 수학 연산을 수행한다. 섹션 3.3.2에서 언급한 것과 같이, “Dawn” 데모에서 정점 쉐이더는 우선 모프 타겟을 적용하고, 98개의 뼈대로 된 180,000개 이상의 메쉬를 스키닝한다. 더 많은 것은 챕터 4를 보라.
For each vertex, we receive from the CPU application the data shown in Listing 3-1. [5]
각 정점에 대해, CPU에게서 Listing 3-1에 있는 어플리케이션 데이터를 받는다.
The factors that are computed in the vertex shader and passed as interpolated values in the fragment shader include the world-space eye direction vector (worldEyeDirection), describing the direction from the viewer's eye to any given vertex; the 3x3 tangent to world-space matrix (tangentToWorld) [6] ; and a variety of terms collectively called blood transmission terms (bloodTransmission). Listing 3-2 shows the data structure of the output vertices.
정점 쉐이더에서 계산된 인자들은 보간된 값으로 단편 쉐이더에 전달된다. 그 값에는 눈에서 어떤 주어진 정점으로의 방향을 나타내는, 월드공간 눈 방향 벡터(worldEyeDirection); 탄젠트 공간에서 월드 공간으로 변환시키는 3x3 행렬(tangentToWorld); 혈색 전달 항이라고 불리는 여러 개의 항(bloodTransmission)이 있다. Listing 3-2는 출력 정점의 데이터 구조를 보여준다.
Example 3-1. The Per-Vertex Data Received from the CPU Application
// Here is the PER-VERTEX data -- we use 16 vectors, // the maximum permitted by our graphics API struct a2vConnector { float4 coord; // 3D location float4 normal; float4 tangent; float3 coordMorph0; // 3D offset to target 0 float4 normalMorph0; // matching offset float3 coordMorph1; // 3D offset to target 1 float4 normalMorph1; // matching offset float3 coordMorph2; // 3D offset to target 2 float4 normalMorph2; // matching offset float3 coordMorph3; // 3D offset to target 3 float4 normalMorph3; // matching offset float3 coordMorph4; // 3D offset to target 4 float4 normalMorph4; // matching offset float4 boneWeight0_3; // skull and neck bone float4 boneIndex0_3; // indices and weights float4 skinColor_frontSpec; // UV indices };
Example 3-2. The Data Structure of the Output Vertices
// Here is the data passed from the vertex shader // to the fragment shader struct v2fConnector { float4 HPOS : POSITION; float4 SkinUVST : TEXCOORD0; float3 WorldEyeDir : TEXCOORD2; float4 SkinSilhouetteVec : TEXCOORD3; float3 WorldTanMatrixX : TEXCOORD5; float3 WorldTanMatrixY : TEXCOORD6; float3 WorldTanMatrixZ : TEXCOORD7; };
Because we are bump mapping, our fragment shader will have to find the world-space bumped normal, so we must provide it a way to get from the tangent-space bumped normal (provided by a texture map) into world space. The common way of doing bump mapping is to have the vertex shader pass the world-space normal, binormal, and tangent, and then to use these three vectors as a 3x3 matrix to rotate vectors from world space into tangent space for computation. In this case, the fragment shader will have to look into the lighting solution using the world-space vectors, so we map the transpose of this matrix (the transpose is the inverse for a rotation matrix), resulting in nine MOV instructions in the vertex shader to load the WorldTanMatrixX, WorldTanMatrixY, and WorldTanMatrixZ terms.
우리는 범프 맵핑을 사용하기 때문에, 우리의 단편 쉐이더는 월드 공간 범프 법선을 찾아야 할 것이다. 그래서 우리는 (텍스쳐맵에서 제공되는)탄젠트 공간 범프 법선에서 월드 공간 범퍼 법선을 얻을 수 있는 방법을 제공해야 한다. 범프 맵핑을 하는 일반적인 방법은 정점 쉐이더가 월드 공간 법선, 종법선, 탄젠트를 전달하여, 벡터를 월드 공간에서 탄젠트 공간으로 회전하기 위해 이 세 벡터를 3x3 행렬로 사용하는 것이다. 이 경우, 단편 쉐이더는 월드 공간 벡터를 사용한 조명 해법을 알아야할 것이다. 그래서 우리는 WorldTanMatrixX, WorldTanMatrixY, WorldTanMatrixZ 항을 만들기 위해 정점 쉐이더에서 9개의 MOV 명령어를 사용해서 이 행렬의 전치(회전 행렬의 경우 역행렬이 전치 행렬이된다)를 구한다.
Finally, the vertex shader blood transmission or "skin silhouette" terms are a float4 vector, composed of the occlusion term; different variations on the expression (N · V) (that is, the dot product of the surface normal and the view vector); and a rotation of the normal against the coordinate system of the cube map lighting. See Figure 3-7.
마지막으로, 정점 쉐이더 혈색 전달 혹은 “피부 실루엣” 항은 폐색항으로 구성된 float4 벡터이다. 즉, 식 (N · V) (즉, 표면 법선과 시야 벡터의 내적) 의 다른 형태이고, 큐브맵 조명의 좌표계를 중심으로 법선을 회전한 것이다. 그림 3-7을 보라.

Figure 3-7 Dot Products Stored in
OUT.SkinSilhouetteVec = float4(objectNormal.w, oneMinusVdotN * oneMinusVdotN, oneMinusVdotN, vecMul(G_DappleXf, worldNormal.xyz).z);
These steps provided just a few ways of parameterizing how near a silhouette the pixel was, and they gave us a toy box of values to play with while developing the fragment shader. Mathematically astute readers may notice that the (N · V) terms may not interpolate correctly on large triangles via Gouraud (linear) shading. Fortunately for this specific case, Dawn is finely tessellated, and the camera needs to be very near her face before any error is apparent. This is a good example of using a shader that is highly specific to a particular model, to be seen in a range of predictable screen sizes. The (N · V) could have been done in the fragment shader, but that would have made for a significantly more expensive (and hence slower) shader.
이 단계는 픽셀이 실루엣에 얼마나 가까운지를 매개변수화하는 극소수의 방법을 제공했고, 단편 쉐이더를 개발하는 대신에 값이 든 장난감 상자를 가지고 놀게 해주었다. 수학적으로 눈치 빠른 독자들은 (N · V)항이 Gouraud (선형) 쉐이딩을 사용했을 때는 큰 삼각형에 정확히 보간되지 않을 것이라는 것을 눈치챘을 것이다. 다행이, 이 특별한 경우에서는, Dawn은 멋지게 테셀레이션되고, 어떤 오류가 뚜렷해지려면 카메라가 그녀의 얼굴에 매우 가까이 가야한다. 이것은 예측할 수 있는 스크린 크기의 범위에서 보여지는 특수한 모델에 매우 특화된 쉐이더를 사용하는 좋은 예이다. (N · V)은 단편 쉐이더에서 계산될 수도 있지만, 훨씬 더 비싼 (그래서 더 느린) 쉐이더를 만들 것이다.
Note that all of the vertex shaders in the "Dawn" demo were procedurally generated. We assign fragment shaders in Maya, and we have a vertex shader generator that looks at the character setup (skeletons, morph targets, and so on) and the inputs requested by the fragment shader; it then generates the optimal vertex shader from a rules file using an A* search. [7]
“Dawn” 데모에 있는 모든 정점 쉐이더는 절차적으로 생성된다는 것을 주목하라. 우리는 Maya에서 단편 쉐이더를 지정하고, 캐릭터 설정(뼈대, 모프 타겟 등등)과 단편 쉐이더에서 필요한 입력을 볼 수 있는 정점 쉐이더 생성기를 가지고 있다. 정점 쉐이더 생성기는 A* 탐색을 사용한 규칙 파일로부터 최적의 정점 쉐이더를 생성한다.
Listing 3-3 is a sample annotated vertex shader, as used on Dawn's face area (incorporating both matrix skinning and shape blends, along with values used for the color calculations in the fragment shader).
Listing 3-3은 Dawn의 얼굴 지역에 사용된 (단편 쉐이더에서 색 계산에 사용된 값에 따라 행렬 스키닝과 형태 혼합을 둘 다 통합한) 주석이 달린 정점 쉐이더의 예이다.
Example 3-3. A Sample Vertex Shader for Dawn's Face
// Helper function: // vecMul(matrix, float3) multiplies like a vector // instead of like a point (no translate) float3 vecMul(const float4x4 matrix, const float3 vec) { return(float3(dot(vec, matrix._11_12_13), dot(vec, matrix._21_22_23), dot(vec, matrix._31_32_33))); } // The Vertex Shader for Dawn's Face v2fConnector faceVertexShader(a2vConnector IN, const uniform float MorphWeight0, const uniform float MorphWeight1, const uniform float MorphWeight2, const uniform float MorphWeight3, const uniform float MorphWeight4, const uniform float4x4 BoneXf[8], const uniform float4 GlobalCamPos, const uniform float4x4 ViewXf, const uniform float4x4 G_DappleXf, const uniform float4x4 ProjXf) { v2fConnector OUT; // The following large block is entirely // concerned with shape skinning. // First, do shape blending between the five // blend shapes ("morph targets") float4 objectCoord = IN.coord; objectCoord.xyz += (MorphWeight0 * IN.coordMorph0); objectCoord.xyz += (MorphWeight1 * IN.coordMorph1); objectCoord.xyz += (MorphWeight2 * IN.coordMorph2); objectCoord.xyz += (MorphWeight3 * IN.coordMorph3); objectCoord.xyz += (MorphWeight4 * IN.coordMorph4); // Now transform the entire head by the neck bone float4 worldCoord = IN.boneWeight0_3.x * mul(BoneXf[IN.boneIndex0_3.x], objectCoord); worldCoord += (IN.boneWeight0_3.y * mul(BoneXf[IN.boneIndex0_3.y], objectCoord)); worldCoord += (IN.boneWeight0_3.z * mul(BoneXf[IN.boneIndex0_3.z], objectCoord)); worldCoord += (IN.boneWeight0_3.w * mul(BoneXf[IN.boneIndex0_3.w], objectCoord)); // Repeat the previous skinning ops // on the surface normal float4 objectNormal = IN.normal; objectNormal += (MorphWeight0 * IN.normalMorph0); objectNormal += (MorphWeight1 * IN.normalMorph1); objectNormal += (MorphWeight2 * IN.normalMorph2); objectNormal += (MorphWeight3 * IN.normalMorph3); objectNormal += (MorphWeight4 * IN.normalMorph4); objectNormal.xyz = normalize(objectNormal.xyz); float3 worldNormal = IN.boneWeight0_3.x * vecMul(BoneXf[IN.boneIndex0_3.x], objectNormal.xyz)); worldNormal += (IN.boneWeight0_3.y * vecMul(BoneXf[IN.boneIndex0_3.y], objectNormal.xyz)); worldNormal += (IN.boneWeight0_3.z * vecMul(BoneXf[IN.boneIndex0_3.z], objectNormal.xyz)); worldNormal += (IN.boneWeight0_3.w * vecMul(BoneXf[IN.boneIndex0_3.w], objectNormal.xyz)); worldNormal = normalize(worldNormal); // Repeat the previous skinning ops // on the orthonormalized surface tangent vector float4 objectTangent = IN.tangent; objectTangent.xyz = normalize(objectTangent.xyz - dot(objectTangent.xyz, objectNormal.xyz) * objectNormal.xyz); float4 worldTangent; worldTangent.xyz = IN.boneWeight0_3.x * vecMul(BoneXf[IN.boneIndex0_3.x], objectTangent.xyz); worldTangent.xyz += (IN.boneWeight0_3.y * vecMul(BoneXf[IN.boneIndex0_3.y], objectTangent.xyz)); worldTangent.xyz += (IN.boneWeight0_3.z * vecMul(BoneXf[IN.boneIndex0_3.z], objectTangent.xyz)); worldTangent.xyz += (IN.boneWeight0_3.w * vecMul(BoneXf[IN.boneIndex0_3.w], objectTangent.xyz)); worldTangent.xyz = normalize(worldTangent.xyz); worldTangent.w = objectTangent.w; // Now our deformations are done. // Create a binormal vector as the cross product // of the normal and tangent vectors float3 worldBinormal = worldTangent.w * normalize(cross(worldNormal, worldTangent.xyz)); // Reorder these values for output as a 3 x 3 matrix // for bump mapping in the fragment shader OUT.WorldTanMatrixX = float3(worldTangent.x, worldBinormal.x, worldNormal.x); OUT.WorldTanMatrixY = float3(worldTangent.y, worldBinormal.y, worldNormal.y); OUT.WorldTanMatrixZ = float3(worldTangent.z, worldBinormal.z, worldNormal.z); // The vectors are complete. Now use them // to calculate some lighting values float4 worldEyePos = GlobalCamPos; OUT.WorldEyeDir = normalize(worldCoord.xyz - worldEyePos.xyz); float4 eyespaceEyePos = {0.0f, 0.0f, 0.0f, 1.0f}; float4 eyespaceCoord = mul(ViewXf, worldCoord); float3 eyespaceEyeVec = normalize(eyespaceEyePos.xyz - eyespaceCoord.xyz); float3 eyespaceNormal = vecMul(ViewXf, worldNormal); float VdotN = abs(dot(eyespaceEyeVec, eyespaceNormal)); float oneMinusVdotN = 1.0 - VdotN; OUT.SkinUVST = IN.skinColor_frontSpec; OUT.SkinSilhouetteVec = float4(objectNormal.w, oneMinusVdotN * oneMinusVdotN, oneMinusVdotN, vecMul(G_DappleXf, worldNormal.xyz).z); float4 hpos = mul(ProjXf, eyespaceCoord); OUT.HPOS = hpos; return OUT; }
3.5.2 The Fragment Shader
Given the outputs of the vertex shader (and everywhere on Dawn's body, the vertex shaders output a consistent data structure), we can generate the actual textured colors.
정점 쉐이더의 출력이 주어지면(그리고 Dawn의 몸의 모든 곳에서, 정점 쉐이더는 동일한 데이터 구조를 출력한다), 실제로 텍스쳐링된 색을 생성할 수 있다.
Listing 3-4 shows the complete fragment shader as used by the face.
Listing 3-4는 얼굴에 사용된 완전한 단편 쉐이더를 보여준다.
Example 3-4. The Fragment Shader for Dawn's Face
float4 faceFragmentShader(v2fConnector IN, uniform sampler2D SkinColorFrontSpecMap, uniform sampler2D SkinNormSideSpecMap, // xyz normal map uniform sampler2D SpecularColorShiftMap, // and spec map in "w" uniform samplerCUBE DiffuseCubeMap, uniform samplerCUBE SpecularCubeMap, uniform samplerCUBE HilightCubeMap) : COLOR { half4 normSideSpec tex2D(SkinNormSideSpecMap, IN.SkinUVST.xy); half3 worldNormal; worldNormal.x = dot(normSideSpec.xyz, IN.WorldTanMatrixX); worldNormal.y = dot(normSideSpec.xyz, IN.WorldTanMatrixY); worldNormal.z = dot(normSideSpec.xyz, IN.WorldTanMatrixZ); fixed nDotV = dot(IN.WorldEyeDir, worldNormal); half4 skinColor = tex2D(SkinColorFrontSpecMap, IN.SkinUVST.xy); fixed3 diffuse = skinColor * texCUBE(DiffuseCubeMap, worldNormal); diffuse = diffuse * IN.SkinSilhouetteVec.x; fixed4 sideSpec = normSideSpec.w * texCUBE(SpecularCubeMap, worldNormal); fixed3 result = diffuse * IN.SkinSilhouetteVec.y + sideSpec; fixed3 hilite = 0.7 * IN.SkinSilhouetteVec.x * IN.SkinSilhouetteVec.y * texCUBE(HilightCubeMap, IN.WorldEyeDir); fixed reflVect = IN.WorldEyeDir * nDotV - (worldNormal * 2.0x); fixed4 reflColor = IN.SkinSilhouetteVec.w * texCUBE(SpecularCubeMap, reflVect); result += (reflColor.xyz * 0.02); fixed hiLightAttenuator = tex2D(SpecularColorShiftMap, IN.SkinUVST.xy).x; result += (hilite * hiLightAttenuator); fixed haze = reflColor.w * hiLightAttenuator; return float4(result.xyz, haze); }
First, we get bump-mapped surface normal. The texture stored in SkinNormSideSpecMap contains tangent-space normals in its RGB components, and the specular map—a grayscale representing highlight intensities—is piggybacking in the alpha channel (we'll refer to the component RGB as xyz here for code clarity). By rotating the tangent-space xyz values against the WorldTanMatrix, we recast them in world coordinates—exactly what we need to perform our world-space lighting algorithm.
우선, 우리는 범프 맵핑된 표면 법선을 얻는다. SkinNormSideSpecMap 에 저장된 텍스쳐는 RGB 성분에 탄젠트 공간 법선을 포함하고, 정반사 맵 —그레이스케일로 나타낸 하이라이트 세기—은 알파 채널에 넣는다 ( 우리는 여기서 코드의 명확성을 위해 RGB 성분을 xyz로 나타낼 것이다). 탄젠트 공간 xyz값은 WorldTanMatrix로 회전시켜 월드 좌표에서 다시 계산한다. 정확히 우리가 필요한 것은 우리의 월드 공간 조명 알고리즘을 수행하는 것이다.
We then compare the newly calculated surface normal to the view direction. We use this nDotV value later.
그런 다음, 우리는 새로 계산된 표면 법선을 시야 방향과 비교한다. 우리는 이 nDotV 값을 나중에 사용한다.
half4 normSideSpec tex2D(SkinNormSideSpecMap, IN.SkinUVST.xy); half3 worldNormal; worldNormal.x = dot(normSideSpec.xyz, IN.WorldTanMatrixX); worldNormal.y = dot(normSideSpec.xyz, IN.WorldTanMatrixY); worldNormal.z = dot(normSideSpec.xyz, IN.WorldTanMatrixZ); fixed nDotV = dot(IN.WorldEyeDir, worldNormal);
Diffuse color is the skin texture map, multiplied by the preconvolved diffuse-lighting cube map. We modulate this a bit by the hemispherical occlusion term passed in SkinSilhouetteVec.
난반사 색은 미리 생성한 난반사-조명 입방체맵을 곱한 피부 텍스쳐 맵이다. 우리는 이것을 SkinSilhouetteVec 에 전달된 반구 폐색항으로 약간 조정한다.
half4 skinColor = tex2D(SkinColorFrontSpecMap, IN.SkinUVST.xy); fixed3 diffuse = skinColor * texCUBE(DiffuseCubeMap, worldNormal); diffuse = diffuse * IN.SkinSilhouetteVec.x;
Edge specular color comes from our specular cube map, modulated by the specular intensity map that we got with the normal map (that is, in the alpha channel of SkinNormSideSpecMap). We start building a cumulative result.
모서리 정반사 색은 법선맵에 있는 정반사 세기 맵(즉, SkinNormSideSpecMap 의 알파 채널)으로 곱한 우리의 정반사 입방체 맵으로 구한다.
fixed4 sideSpec = normSideSpec.w * texCUBE(SpecularCubeMap, worldNormal); fixed3 result = diffuse * IN.SkinSilhouetteVec.y + sideSpec;
Next, we retrieve the color of the environment behind Dawn, by indexing on WorldEyeDir, and we get the traditional reflection cube-map color. Add these, along with some artistic "fudge factoring," to our result.
그 다음, WorldEyeDir 로 색인하여 Dawn 뒤의 환경색을 구하여, 전통적인 반사 입방체맵 색을 얻는다. 얼마간의 예술적 “오차”에 따라 우리의 결과에 이것을 더한다.
fixed3 hilite = 0.7 * IN.SkinSilhouetteVec.x * IN.SkinSilhouetteVec.y * texCUBE(HilightCubeMap, IN.WorldEyeDir); fixed reflVect = IN.WorldEyeDir * nDotV - (worldNormal * 2.0x); fixed4 reflColor = IN.SkinSilhouetteVec.w * texCUBE(SpecularCubeMap, reflVect); result += (reflColor.xyz * 0.02); fixed hiLightAttenuator = tex2D(SpecularColorShiftMap, IN.SkinUVST.xy).x; result += (hilite * hiLightAttenuator);
Finally, we add a little extra silhouette information into the alpha channel of the final output, so that the "bloom" along Dawn's silhouette edges looks more natural when alpha blending.
마지막으로, 최종 출력의 알파 채널에 약간의 특별한 실루엣 정보를 추가한다. 그로 인해 알파 블렌딩시 Dawn의 실루엣 모서리를 따라 있는 “홍조(Bloom)”이 더 자연스럽게 보인다.
fixed haze = reflColor.w * hiLightAttenuator; return float4(result.xyz, haze);
3.6 Conclusion
Although many of the implementation details in the Dawn skin shaders may be too restrictive for many game engines, most of the concepts can be achieved using other means. The fundamentals of high dynamic range, subsurface scattering, rim lighting, and the like can also be computed from synthetic light sources or other scene information.
Dawn 피부 쉐이더에 사용된 많은 세부 구현들이 많은 게임엔진에 너무 제한적이긴 하지만, 대부분의 개념이 다른 방법을 사용하여 이루어질 수 있다. HDR, 표면내부 산란, 가장자리 조명의 원리는 통합 광원이나 다른 장면 정보를 이용해서도 계산할 수 있다.
In many ways, it was difficult to work with Dawn being lit by the environment. More complex and more realistic lighting solutions often come at the expense of artistic control. In this instance, we wanted her goose bumps to be more visible, but the environment was diffuse enough that we had to unrealistically exaggerate her surface bump to compensate.
많은 이유로, 환경에 의해 조명을 받는 Dawn을 가지고 작업하는 것이 어려웠다. 예술적 조절을 통해 더 복잡하고 실제 같은 조명 해법을 수행할 있다. 이번 경우에, 우리는 그녀의 닭살이 더 잘 보이길 원했지만, 그녀의 표면 범프를 비현실적으로 과장하여 보정하기에 환경이 충분히 난반사되었다.
If we were to implement Dawn a second time, we would probably use a more hybrid approach to lighting, in which we would look up into the diffuse and specular map (given the smooth normal) and then use a primary "light direction" to compute the contribution of the bump map. This would give us more direct control over the look of the bump map and eliminate the need for the expensive matrix transpose performed in the vertex shader.
만약 우리가 Dawn을 다시 구현했다면, 아마 난반사와 정반사 맵을 (주어진 부드러운 법선으로) 검색한 다음 범프맵의 기여도를 계산하기 위해 주요 “조명 방향”을 사용하는 더 혼합적인 접근법을 조명에 사용했을 것이다. 이것은 범프맵의 전체적인 모양에 더 직접적인 제어를 할 수 있게 하고 정점 쉐이더에서 수행되는 값비싼 행렬 전치의 필요성을 없앤다.
3.7 References
Stout, Bryan. 1996. "Smart Moves: Intelligent Pathfinding." Game Developer, October 1996. Available online at the Gamasutra Web site: http://www.gamasutra.com/features/19970801/pathfinding.htm
Copyright
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and Addison-Wesley was aware of a trademark claim, the designations have been printed with initial capital letters or in all capitals.
The authors and publisher have taken care in the preparation of this book, but make no expressed or implied warranty of any kind and assume no responsibility for errors or omissions. No liability is assumed for incidental or consequential damages in connection with or arising out of the use of the information or programs contained herein.
The publisher offers discounts on this book when ordered in quantity for bulk purchases and special sales. For more information, please contact:
U.S. Corporate and Government Sales
(800) 382-3419
corpsales@pearsontechgroup.com
For sales outside of the U.S., please contact:
International Sales
international@pearsoned.com
Visit Addison-Wesley on the Web: www.awprofessional.com
Library of Congress Control Number: 2004100582
GeForce™ and NVIDIA Quadro® are trademarks or registered trademarks of NVIDIA Corporation.
RenderMan® is a registered trademark of Pixar Animation Studios.
"Shadow Map Antialiasing" © 2003 NVIDIA Corporation and Pixar Animation Studios.
"Cinematic Lighting" © 2003 Pixar Animation Studios.
Dawn images © 2002 NVIDIA Corporation. Vulcan images © 2003 NVIDIA Corporation.
Copyright © 2004 by NVIDIA Corporation.
All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form, or by any means, electronic, mechanical, photocopying, recording, or otherwise, without the prior consent of the publisher. Printed in the United States of America. Published simultaneously in Canada.
For information on obtaining permission for use of material from this work, please submit a written request to:
Pearson Education, Inc.
Rights and Contracts Department
One Lake Street
Upper Saddle River, NJ 07458
Text printed on recycled and acid-free paper.
5 6 7 8 9 10 QWT 09 08 07
5th Printing September 2007