ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Effective Water Simulation from Physical Models
    Graphics/Graphics Study 자료 2020. 11. 18. 23:59

     

    Effective Water Simulation from Physical Models

    최초 작성 : 2020-11-18

    마지막 수정 : 2020-11-18

    최재호

     

    목차

    1. 목표

    2. 내용

      2.1 파의 중첩

        2.1.1 기본 아이디어

        2.1.2 마루에서 더 날카로워지는 Wave 성질 추가

        2.1.3 방향성 또는 원형의 Wave

        2.1.4 실제 사용할 함수 정리

        2.1.5 환경맵의 사용과 차이점

    3. 실제 구현

      3.1 GeoWave 구현

      3.2 TexWave 구현

        3.2.1 TexWave에 사용한 식

        3.2.2 CosineLUT 생성

        3.2.3 CosineLUT 사용

        3.2.4 NoiseLUT 생성과 적용

        3.2.5 생성된 Normal Map의 사용 방식

        3.2.6 기타

    4. 구현 결과

    5. 실제 구현 코드

    6. 레퍼런스

     

    1. 목표

    이 글은 GPU Gems1의 1번째 챕터 Effective Water Simulation from Physical Models 를 이해하고 구현해보는 것이 목표입니다.

     

     

    2. 내용

    Wave는 여러 개의 Sin을 통해 정의합니다. 이 Sin으로 만들어진 Wave의 요소들을 정의하는 것이 중요합니다.

     

    • WaveLength(L) : 월드 공간에서 두 Wave 사이의 마루에서 마루까지의 거리
    • Frequency(w) : 파장 L은 빈도 w = 2π/L (2π 당 Wave 몇 번이나 존재하느냐?)
    • Amplitude(A) : 물의 표면에서 Wave 마루까지의 높이

     

    그림1. Sin Wave 함수의 파라메터 (출처 : 레퍼런스 1)

     

     

    2.1 파의 중첩

    2.1.1 기본 아이디어

    수평 위치 (x, y)와 시간 t 에 따른 Wave의 Height를 구하는 식은 아래와 같습니다. 이 W에 표시된 i 번째 Wave의 높이라는 의미입니다

     

    그림2. Wave 의 Height를 구하는 식 (출처 : 레퍼런스 1)

     

    실제로는 여러 Wave가 합쳐지기 때문에 최종 Wave의 높이는 그림3과 같을 것입니다.

     

    그림3. Wave 높이의 합산 (출처 : 레퍼런스 1)

     

    위의 식으로 우리는 Wave 위의 특정 점의 x, y, z를 그림4와 같이 정의할 수 있습니다.

     

    그림4. Wave 위의 특정 위치를 구하면 함수 P

     

    그림4에서 구한 점 P에서의 Normal 값을 구합니다. 이 Normal 값은 물에 반사되는 환경을 렌더링 하는 데 사용합니다. P 함수를 x, y 편미분 하여 먼저 Binormal, Tangent를 구합니다. 그리고 Binormal과 Tangent의 Cross Product로 Normal을 구해냅니다.

     

    그림5. P함수를 X축으로의 편미분
    그림6. P함수를 Y축으로의 편미분
    그림7. Cross(Binormal, Tangent)로 부터 Wave 표면의 점 P에서의 Normal을 얻음

     

     

    2.1.2 마루에서 더 날카로워지는 Wave 성질 추가

    보통의 Sin 함수를 그대로 사용하면, 파형이 너무 부드러워서 실제 Wave보다 부드럽게 보입니다. Wave의 마루가 더 날카롭게 보이게 하기 위해서 그림2의 Wi 식을 그림8과 같이 바꿀 수 있습니다.

     

    그림8. Choppiness(마루에서 가파름)을 추가해주도록 Wi함수 변형

     

    변경된 Wi 함수에 따라서 Normal을 구할 때 사용한 편미분 함수도 달라집니다. 예로 x축에 대한 편미분은 아래와 같습니다.

     

    그림9. Choppiness가 포함된 Wi 함수

     

    이 식에서 k값이 높아질수록 Sin의 마루 부분이 더 뾰족해지는 것을 그림10에서 확인할 수 있습니다.

     

    그림10. k가 높아짐에 따라 Choppiness가 더 커지는 것을 보여줌

     

    2.1.3 방향성 또는 원형의 Wave

    파의 방향성은 그림2의 Wi 에서 D 에 대응하는 함수입니다. 방향성은 단일 방향과 원형 2종류가 있습니다. 방향성은 방향벡터 x, y 로 표현 가능하며, 원형파의 경우 아래 그림11과 같이 표현 가능합니다.

     

    그림11. 원형파의 식
    그림12. 최종적으로는 이 파의 방향성이 이 그림 처럼 합산되어 표현됨 (좌측 : 방향성, 우측 : 원형)

     

     

    2.1.4 실제 사용할 함수 정리

    이제까지 기본적인 Wave의 구현을 보았습니다. 여기서 실제 구현은 그림14의 Gerstner Waves 를 사용합니다. 이 함수의 장점은 그림13과 같이 마루에서 Choppiness효과를 그리고 골(trough)를 더 넓게 만들어 줍니다. Gerstner는 Qi (Qi = 1/(wi * Ai)) 항목으로 Wave의 가파름(Choppiness) 정도를 조정할 수 있습니다. 

     

    그림13. Gerstner Wave
    그림14. Gerstner Wave의 점 P를 정의하는 함수

     

    이 식의 특징은 그림15 처럼 식처럼 P(x, y, t) 를 통해 유도된 x, y 위치가 달라진다는 점이다. 기존 식은 x, y 위치가 변하진 않으며, 단지 Wave의 높이만 구하는 형태였다.

     

    그림15. 점 P에서의 x
    그림16. Gerstner Wave로 부터 Binormal, Tangent, Normal을 구하는 식, (기본 아이디어와 동일하게 편미분을 통해 유도됩니다)

     

     

    2.1.5 환경맵의 사용과 차이점

    이제 Wave의 표면의 특정점 P와 그 점에서의 Normal을 구했습니다. 이 정보들로 물의 표면에 반사되어 비치는 환경을 표현할 수 있을 것입니다. 여기서는 환경맵을 사용하여 이 기능을 구현합니다.

     

    환경맵은 기본적으로 아주 먼 거리에 있는 원경이라고 가정합니다. 그래서 내 위치가 변경되어도 환경맵의 위치는 변경되지 않습니다. (배경이 날 따라다니는 것처럼, 이동해도 환경맵은 같은 장면을 보고 있게 됨). 하지만 물에 반사되는 환경의 경우, 내 위치가 변경되면 반사되어 보이는 위치도 틀려지게 됩니다. 이 부분을 보정해줍니다.

     

    아래 그림17의 (a)에서 점 E에서 점 P를 바라본다고 합시다. 환경맵에서 E→P 방향의 벡터로 Fetch를 시도하며 기존 방식대로 한다고 해봅시다. 실제 환경맵을 Fetch 할 때 우리는 환경맵의 중앙점 C에서 Fetch를 합니다. 그래서 실제로 E→P 방향으로 환경맵을 Fetch 하게 되면, 환경맵상 B 점을 얻게 됩니다. 이 부분을 보정해주기 위해서 C→A 방향의 벡터로 변경해주는 것이 이 문제를 해결하는 핵심이다.

     

    그림17. (a) : 카메라의 실제위치(E)에서 점 P를 바라보는 경우 환경맵의 A값을 보게 됨(이 벡터를 E->P 라 둠), (b) : 실제 환경맵 사용시 환경맵의 촬영당시 중심위치 C에서 (E->P) 방향으로 환경맵을 Fetch함. (c) : 환경맵의 중심 점 C에서 A 쪽을 바라볼 수 있도록 수정
    그림18. (A - C) 의 유도

     

     

    3. 실제 구현

    이제 지금까지 가진 정보들로 실제 구현을 해봅니다. Wave는 2가지 형태로 구성됩니다. GeoWave와 TexWave입니다.

    • GeoWave는 실제 버택스들에 Wave 식들을 적용합니다. 상대적으로 큰 파도 모양을 담당합니다. 이 예제에서는 4개의 GeoWave를 중첩시킵니다.
    • TexWave는 텍스쳐에 저장하는 Wave입니다. Normal Map에 Wave의 표면 Normal을 저장하여 사용합니다. 이 예제에서는 16개의 TexWave를 지원합니다.

     

    전체 렌더링 패스

    • 전처리
      • CosineLUT 생성 (CosineMap이라고 불러도 무방)
      • NoiseMap 생성
    • 렌더링 패스
      • Texture Wave Normal Map 렌더링
        • Texture Wave 4패스 렌더링
          • 4개의 Wave 씩 4패스의 Wave Normal을 텍스쳐에 렌더링함 (4 * 4 = 총 16 Wave 중첩)
          • 첫 패스는 AlphaBlend(Add) : Src One, Dest Zero, 이후 모든 패스는 Src One, Dest One 으로 중첩
        • 마지막 5 패스에 Noise 추가
          • 최종 결과는 Wave의 표면 NormalMap
          • 마찬가지로 AlphaBlend(Add) : Src One, Dest One으로 중첩
      • Geometry Wave 1 패스
        • 총 4개의 Wave를 중첩 (버택스를 직접 이동시킴)
        • 픽셀 쉐이더에서 Texture Wave 패스에 만든 NormalMap을 사용하여 16개의 Wave 추가
          • GeoWave가 채워주지 못하는 잔물결 정보를 환경맵을 Fetch 할 Normal을 수정하는 형태로 하는 것이라 실제 높이 차이는 발생하지 않음.

     

    3.1 GeoWave 구현

    GeoWave는 x, y가 주어졌을 때 특정 점 P를 구할 수 있는 있는 그림14의 식과 그 점 P에서 Normal을 구하는 그림16을 사용하여 구현합니다. 이 식들을 버택스 쉐이더의 정점의 위치를 결정하는 데 사용합니다. 그래서 대부분의 Wave 중첩 계산은 버택스 쉐이더에서 일어나며, 픽셀 쉐이더는 Wave의 표면 Normal을 기준으로 물에 반사되어 비치는 환경맵의 계산에 집중합니다.

     

    전체적인 버택스 쉐이더의 패스를 주석으로 정리해뒀습니다. 코드를 가져오다가 빠뜨린 부분(Fog 같은 부분)이 있을 수도 있는데 대부분 그대로 넣었습니다. 버택스 쉐이더를 실행하는데 필요한 Uniform 변수의 수가 굉장히 많은데, 실제 연산을 하나씩 대조해보면 위에서 언급했던 Gerstner Waves를 사용한 것을 확인할 수 있습니다.

     

    // geowave_vs.glsl
    
    void main()
    {
    	// 1. 월드 공간으로 변형시키고 이후 계산은 모두 월드공간에서 계산 됨.
    	// Evaluate world space base position. All subsequent calculations in world space.
    	vec4 wPos = Local2World * vec4(Pos, 1.0);
    
    	// 2. Calculate ripple UV from position
    	// SpecAtten.w 는 RippleScale : UV 좌표를 늘려서 TexWave의 Wave Length를 전체적으로 조절 
    	TexCoord0_.xy = wPos.xy * SpecAtten.ww;
    	TexCoord0_.z = 0.f;
    	TexCoord0_.w = 1.f;
    
    	// 3. Get our depth based filters. 
    	// cDepthOffset,
    	// cDepthScale,
    	// vec4 depthOffset, vec4 depthScale, vec4 wPos, float waterLevel
    	// 깊이 기반으로 필터를 구해낸다. 0~1 사이 값으로 얻어냄
    	// [overall opacity, reflection strength, wave height]
    	vec3 dFilter = CalcDepthFilter(DepthOffset, DepthScale, wPos);
    
    	// Build our 4 waves
    
    	vec4 sines;
    	vec4 cosines;
    
    	vec4 Color = vec4(1.0);
    
    	// 4. Amp * Cos, Amp * Sin 구하는 것.
    	CalcSinCos(wPos,
    		DirX, DirY,
    		Amplitude, Frequency, Phase,
    		Lengths, Color.a,
    		dFilter.z,		// dFilter.z => wave height
    		sines, cosines);
    
    	// 5. Equation 9 구하기
    	wPos = CalcFinalPosition(wPos, sines, cosines, DepthOffset.w, DirXK, DirYK);
    
    	// 6. CameraToVertex 와 TexWave NormalMap의 감쇄 정보를 계산합니다.
    	// 최종 위치를 가진다. 우리는 카메라에서 버택스로의 정규화된 벡터가 여러번 필요합니다. 그래서 여기 만듭니다.
    	// We have our final position. We'll be needing normalized vector from camera 
    	// to vertex several times, so we go ahead and grab it.
    	vec3 cam2Vtx;
    	float pertAtten;
    	CalcEyeRayAndBumpAttenuation(wPos, CameraPos, SpecAtten, cam2Vtx, pertAtten);
    
    	// Compute our finitized eyeray.
    	// 7. 환경맵을 현재 위치에 맞게 보정하는 코드. Eye Vector 파트 보면 됨.
    	vec3 eyeRay = FinitizeEyeRay(cam2Vtx, EnvAdjust);
    
    	// 8. Equation 10 구하기, Binormal, Tangent, Normal 벡터를 구합니다.
    	vec3 norm;
    	CalcTangentBasis(sines, cosines,
    		DirXSqKW,
    		DirXdirYKW,
    		DirYSqKW,
    		DirXW,
    		DirYW,
    		KW,
    		pertAtten,
    		eyeRay,
    		BTN_X_,
    		BTN_Y_,
    		BTN_Z_,
    		norm);
    
    	vec4 Position;
    
    	// 9. ViewProjection Matrix를 곱해 Pos -> NDC 공간으로 이동시키고, Fog 정보를 계산합니다. (하지만 이 구현에서 Fog는 제외시켰음)
    	// Calc screen position and fog
    	// Screen 위치와 Fog (NDC의 Z 기준, W로 나누기 전이기 때문이므로 Z가 들어있을 것임)
    	CalcScreenPosAndFog(World2NDC, FogParams, wPos, Position, Fog_);
    
    	// 10. 최종 컬러를 연산합니다.
    	// 버택스기준으로 프레넬 효과를 적용하고, 깊이값을 기준으로 컬러와 투명도를 처리합니다.
    	CalcFinalColors(norm,
    		cam2Vtx,
    		Color.b,
    		Color.r,
    		dFilter.y,		// dFilter.y => reflection strength
    		dFilter.x,		// dFilter.x => overall opacity
    		WaterTint,
    		ModColor_,
    		AddColor_);
    
    	gl_Position = Position;
    }

     

    픽셀 쉐이더에서는 현재 픽셀의 표면 기준으로 TexWave를 옮기기 위해서 TBN 매트릭스를 전달받아 연산하는 것을 볼 수 있습니다. 그리고 glsl의 reflect 함수가 픽셀 쉐이더 1.1에 있는 연산과 틀려서 픽셀쉐이더 1.1의 반사 연산을 그대로 가져와서 적용했습니다.

     

    또한 discard 구문이 있는데, 물의 경우 반투명이기 때문에 삼각형을 거리에 따라 정렬해서 차례로 그려야 문제가 없습니다. 하지만 여기서는 간단히 구현을 마치기 위해 투명한 삼각형인 경우만 알파 테스트를 실행하여 처리하였습니다. (이 부분은 파도가 크게 치거나 할 때 좋지 않은 모습을 보이긴 해서 추후 시간이 날 때 수정할 예정입니다.)

    #version 330 core
    
    precision highp float;
    
    uniform samplerCube tex_object;         // Environment map
    uniform sampler2D tex_object3;          // Bump map
    uniform vec4 ReScale;
    
    in vec4 ModColor_;
    in vec4 AddColor_;
    in float Fog_;
    in vec4 TexCoord0_;
    
    out vec4 color;
    
    in vec4 BTN_X_; // Binormal.x, Tangent.x, Normal.x
    in vec4 BTN_Y_; // Binormal.x, Tangent.x, Normal.x
    in vec4 BTN_Z_; // Binormal.x, Tangent.x, Normal.x
    
    void main()
    {
        vec4 TextureNormal = texture(tex_object3, TexCoord0_.xy);
    
        float u = dot(BTN_X_.xyz, TextureNormal.xyz);
        float v = dot(BTN_Y_.xyz, TextureNormal.xyz);
        float w = dot(BTN_Z_.xyz, TextureNormal.xyz);
    
        //float u = dot(vec3(BTN_X_.x, BTN_Y_.x, BTN_Z_.x), TextureNormal.xyz);
        //float v = dot(vec3(BTN_X_.y, BTN_Y_.y, BTN_Z_.y), TextureNormal.xyz);
        //float w = dot(vec3(BTN_X_.z, BTN_Y_.z, BTN_Z_.z), TextureNormal.xyz);
    
        vec3 Normal = (vec3(u, v, w));
    
        vec3 view = (vec3(BTN_X_.w, BTN_Y_.w, BTN_Z_.w));
        vec3 vReflect;// = reflect(-view, Normal);
        vReflect = 2.0 * dot(Normal, view) / dot(Normal, Normal) * Normal - view;
        color = (ModColor_ * texture(tex_object, vReflect) + AddColor_);
        if (ModColor_.a <= 0.0)
            discard;
        color.a = ModColor_.a;
    }

     

    3.2 TexWave 구현

    3.2.1 TexWave에 사용한 식

    TexWave는 16개의 Wave를 중첩시킵니다. TexWave는 총 5개의 렌더 패스를 가집니다.

    처음 4개의 Pass, Wave의 Normal을 중첩시킵니다. 그리고 마지막 패스에서 Noise를 추가합니다.

     

    최종 GeoWave의 렌더링 시 TexWave의 Normal Map을 사용하여 환경맵을 Fetch 하는 데 사용합니다. 텍스쳐 공간이다 보니 x, y대신 u, v를 사용하여 표현합니다.

     

    그림19. TexWave에 사용되는 Wave 식

     

    TexWave의 Cos 함수를 더 빠르게 사용하기 위해서 cos 함수를 룩업 텍스쳐로 만듭니다. 아마도 이 당시는 삼각함수가 상당히 성능이 느려서 이렇게 더 신경 쓰지 않았을까 합니다. 또한 이 룩업 테이블만 바꿔서 Choppiness 가 적용되는 Wi 함수로 교체가 가능한 이점도 있습니다.

     

    Cos 내부 항은 2D Vector입니다. 그래서 2D Texture 형태로 CosineLUT를 만들고 Fetch 할 수 있습니다.

     

    그림20. 그림19 식의 Cos 내부 항

     

    만약 Choppiness 를 적용하고 싶다면, CosineLUT를 만들 때 그림21의 식으로 생성하면 됩니다. 실제 구현에는 Choppiness가 반영된 식을 사용합니다.

     

    그림21. Choppiness 를 적용한 식

     

     

    3.2.2 CosineLUT 생성

    CosineLUT 를 만드는 식은 아래와 같습니다.

    // Equation 14에 있는 공식임. 이름이 CosineLUT 이지만 실제로는 pow(sine, n) * cosine 임 Choppiness를 반영하기 위해서임
    // (cos * sin^n, cos * sin^n, 1.0, 1.0)
    // => (cos(2PI * u) * ((sin(2PI * u) + 1) / 2)^k, cos(2PI * u) * ((sin(2PI * u) + 1) / 2)^k, 1.0, 1.0)
    static jTexture* CosineLUT = nullptr;
    static bool IsCreatedCosineLUT = false;
    if (!IsCreatedCosineLUT)
    {
        jImageData cosineLUTData;
        cosineLUTData.Width = kBumpTexSize;
        cosineLUTData.Height = 1;
        cosineLUTData.ImageData.resize(kBumpTexSize * 4);		// RGBA 이기 때문에 4를 곱함
        uint32* pDat = (uint32*)&cosineLUTData.ImageData[0];
        for (int32 i = 0; i < kBumpTexSize; i++)
        {
            float dist = float(i) / float(kBumpTexSize - 1) * 2.f * PI;	// [0 ~ kBumpTexSize] -> [0 ~ 2PI]
            float c = float(cos(dist));
            float s = float(sin(dist));
            s *= 0.5f;			// sine(2 * PI * u) => sine(2 * PI * u) / 2
            s += 0.5f;			// sine(2 * PI * u) / 2 => sine(2 * PI * u) / 2 + 1 / 2 = (sine(2 * PI * u) + 1) / 2
            s = float(pow(s, TexState.Chop));
            c *= s;
    
            // BYTE 형태의 텍스쳐에 쓰기 위해서, [-1.0, 1.0] -> [0.0, 1.0] -> [0, 255]로 범위 변경
            // 이 값이 쉐이더로 들어가면 [0.0, 1.0]의 값으로 변경될 것임
            unsigned char cosDist = (unsigned char)((c * 0.5 + 0.5) * 255.999f);
    
            // ARGB -> ABGR
            //pDat[i] = (0xff << 24)
            //	| (cosDist << 16)
            //	| (cosDist << 8)
            //	| 0xff;
    
            // ABGR 순으로 되어있기 때문에 그에 맞게 수정
            pDat[i] = (0xff << 24)
                | (0xff << 16)
                | (cosDist << 8)
                | cosDist;
    
        }
        CosineLUT = g_rhi->CreateTextureFromData(&cosineLUTData.ImageData[0]
            , cosineLUTData.Width, cosineLUTData.Height, false);
        IsCreatedCosineLUT = true;
    }

     

    3.2.3 CosineLUT 사용

    Cos의 내부항은 Shader 변수 UTrans를 통해서 전달됩니다.

    이 버택스 쉐이더는 Wave의 Normal을 중첩시키는 쉐이더입니다. 버택스 쉐이더에서는 Cos 내부항을 구하고 이 것을 픽셀 쉐이더에서 CosineLUT를 룩업 하는데 활용합니다. 또한 픽셀 쉐이더는 Cos의 외부항에 해당하는 것들 또한 추가로 계산합니다.

    // drawtexwave_vs.glsl
    
    VertOut Func(vec4 position, vec2 texCoord, vec4 rot0, vec4 rot1, vec4 rot2, vec4 rot3)
    {
    	VertOut Out;
    	Out.Position = position;
    
    	vec4 uv = vec4(0.0f, 0.0f, 0.0f, 1.f);
    	vec4 texCoord4 = vec4(texCoord, 0.0, 1.0);
    
    	uv.x = dot(texCoord4, rot0);
    	Out.Uv0 = uv;
    
    	uv.x = dot(texCoord4, rot1);
    	Out.Uv1 = uv;
    
    	uv.x = dot(texCoord4, rot2);
    	Out.Uv2 = uv;
    
    	uv.x = dot(texCoord4, rot3);
    	Out.Uv3 = uv;
    
    	return Out;
    }
    
    ...
        // UTrans : RotScale.x, RotScale.y, 0, Phase
    	// Cos 내부 dir.xy를 RotScale.xy로 나타냄, 실제 dir과 rotscale의 차이는 rotscale이 정규화된 사이즈라는 것임
    	// Cos 내부의 dot(dir.xy, uv) + phase 식을 계산 함
    	VertOut result = Func(Position, TexCoord, UTrans[0], UTrans[1], UTrans[2], UTrans[3]);
    ...

    Cos 외부의 항은 Shader 변수 Coef 를 통해서 전달됩니다. 실제 그림22의 외부항에 포함된 모든 파라메터를 사용하진 않고 있습니다. Di 와 NormalScale만 사용하는데 아마 NormalScale이 Amp를 다루고 있을 것 같고, w항은 들어가지 않을 것으로 보입니다. 다른 코드에서 활용하고 있을 순 있는데, 원본 코드에서 특별히 해당 부분을 찾을 수 없었습니다.

     

    그림22. 그림19의 식 외부항

     

    // drawtexwave_fs.glsl
    // Shader 당 4개의 Wave 정보가 계산됨. x, y, z, w에 각각의 파도의 방향이 들어있음.
    ...
        // Equation 14 번
        // Coef : Dir.x * normalScale, Dir.y * normalScale, 1, 1
        // cos(D * (uv) + phase) * Coef
        // = cos(D * (uv) + phase) * (Dir.xy * normalScale)
        // 여기서 Dir.xy 에 w와 A가 곱해져야 하는데 빠져있는 상태, NormalScale이 Amp 정보일 순 있음
        vec4 result = cos0 * Coef[0] + cos1 * Coef[1] + cos2 * Coef[2] + cos3 * Coef[3];
    ...

     

    3.2.4 NoiseLUT 생성과 적용

    NoiseTexture는 x, y 컴포넌트에 0~255 사이 값을 랜덤으로 입력합니다.

    jImageData BiasNoiseData;
    BiasNoiseData.Width = kBumpTexSize;
    BiasNoiseData.Height = kBumpTexSize;
    BiasNoiseData.ImageData.resize(kBumpTexSize * 4 * kBumpTexSize);		// RGBA 이기 때문에 4를 곱함
    uint32* pDat = (uint32*)&BiasNoiseData.ImageData[0];
    for (int32 i = 0; i < kBumpTexSize; i++)
    {
        for (int32 j = 0; j < kBumpTexSize; j++)
        {
            float x = RandZeroToOne();
            float y = RandZeroToOne();
    
            unsigned char r = (unsigned char)(x * 255.999f);
            unsigned char g = (unsigned char)(y * 255.999f);
    
            // ARGB -> ABGR
            //pDat[j + i * kBumpTexSize] = (0xff << 24)
            //	| (r << 16)
            //	| (g << 8)
            //	| 0xff;
    
            // ABGR 순으로 되어있기 때문에 그에 맞게 수정
            pDat[j + i * kBumpTexSize] = (0xff << 24)
                | (0xff << 16)
                | (g << 8)
                | r;
        }
    }
    
    BiasNoise = g_rhi->CreateTextureFromData(&BiasNoiseData.ImageData[0]
        , BiasNoiseData.Width, BiasNoiseData.Height, false);

     

    NoiseTexture는 TexWave의 NormalMap을 만들 때 마지막 패스에서 사용합니다. PostProcess 과정으로 텍스쳐 텍셀을 대상으로 NoiseTexture에서 Noise를 Fetch하여 Noise를 더해줍니다. (Alpha Blend Function은 Add 에 Src One, Dest One)

    // drawtexwave_last_fs.glsl
    
    void main()
    {
    	// 2개의 Noise 를 Fetch하여 혼합
    	vec4 noise0 = texture(tex_object2, TexCoord0_.xy);
    	vec4 noise1 = texture(tex_object2, TexCoord1_.xy);
    
    	vec4 result;
    	result = noise0 - vec4(0.5) + noise1 - vec4(0.5);
    	result.a = noise0.a + noise1.a;
    	FragColor = result * DiffuseColor_ + SpecularColor_;
    }
    

     

    3.2.5 생성된 Normal Map의 사용 방식

    일반 노멀맵과 사용방법은 동일합니다.

    1. GeoWave 패스의 픽셀 쉐이더에서 GeoWave의 표면을 기반으로 한 TBN 매트릭스를 버택스 쉐이더로부터 전달받습니다.
    2. 노멀맵에서 Fetch해온 노멀과 곱하여 월드 공간에서의 Normal을 구해낸다(TBN이 월드 공간 기준이라 바로 월드 공간으로 감). 여기서 구해진 World Normal 정보를 EnvironmentMap을 통해 물에 반사되는 위치 Fetch 하는 데 사용합니다.

     

    3.2.6 기타

    GeoWave의 버택스 쉐이더에서 재미있는 코드도 있었습니다. 예전에는 cos 연산이 비쌌는지 근사하여 계산하는 코드였습니다. 실제 코드는 아래와 같으며 그래프로 그려보면 Sin과 Cos 이 -PI ~ PI 에서 잘 나오는 것을 확인할 수 있습니다. 그림23에 그래프가 있습니다.

    //geowave_vs.glsl
    
    ...
    // 3. cos, sin 구하는 식이며, -pi~pi 사이에 공식이 들어오면 sin cos 를 구해줌
    	// [
    	vec4 dists2 = dists * dists;
    	vec4 dists3 = dists2 * dists;
    	vec4 dists4 = dists2 * dists2;
    	vec4 dists5 = dists3 * dists2;
    	vec4 dists6 = dists3 * dists3;
    	vec4 dists7 = dists4 * dists3;
    
    	const vec4 kSinConsts = vec4(1.f, -1.f / 6.f, 1.f / 120.f, -1.f / 5040.f);
    	const vec4 kCosConsts = vec4(1.f, -1.f / 2.f, 1.f / 24.f, -1.f / 720.f);
    	sines = dists + dists3 * kSinConsts.yyyy + dists5 * kSinConsts.zzzz + dists7 * kSinConsts.wwww;
    	cosines = kCosConsts.xxxx + dists2 * kCosConsts.yyyy + dists4 * kCosConsts.zzzz + dists6 * kCosConsts.wwww;
    	// ]
    ...

     

     

    그림23. Sin, Cos 근사 공식

     

     

    4. 구현 결과

     

    그림24. 최종결과
    그림25. 최종결과2
    그림26. 최종결과 - Choppniess 효과 적용
    그림27. 최종결과 - TexWave -> GeoWave -> GeoWave Move -> Choppniess 순으로 시연

     

     

    5. 실제 구현 코드

    https://github.com/scahp/Shadows/tree/WaveWithPhysicalModel

     

    6. 레퍼런스

    1. [번역]Chapter 1. Effective Water Simulation from Physical Models

    2. https://www.khanacademy.org/science/physics/mechanical-waves-and-sound/harmonic-motion/v/phase-constant

    댓글

Designed by Tistory & scahp.