water ripples (experimental)
parent
3ec703e6af
commit
a461b282c1
@ -0,0 +1,212 @@
|
|||||||
|
#include "ripplesimulation.hpp"
|
||||||
|
|
||||||
|
#include <OgreTextureManager.h>
|
||||||
|
#include <OgreStringConverter.h>
|
||||||
|
#include <OgreHardwarePixelBuffer.h>
|
||||||
|
#include <OgreRoot.h>
|
||||||
|
|
||||||
|
#include <extern/shiny/Main/Factory.hpp>
|
||||||
|
|
||||||
|
namespace MWRender
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
RippleSimulation::RippleSimulation(Ogre::SceneManager* mainSceneManager)
|
||||||
|
: mMainSceneMgr(mainSceneManager),
|
||||||
|
mTime(0),
|
||||||
|
mCurrentFrameOffset(0,0),
|
||||||
|
mPreviousFrameOffset(0,0),
|
||||||
|
mRippleCenter(0,0),
|
||||||
|
mTextureSize(512),
|
||||||
|
mRippleAreaLength(1000),
|
||||||
|
mImpulseSize(20),
|
||||||
|
mTexelOffset(0,0)
|
||||||
|
{
|
||||||
|
Ogre::AxisAlignedBox aabInf;
|
||||||
|
aabInf.setInfinite();
|
||||||
|
|
||||||
|
|
||||||
|
mHeightToNormalMapMaterial = Ogre::MaterialManager::getSingleton().getByName("HeightToNormalMap");
|
||||||
|
mHeightmapMaterial = Ogre::MaterialManager::getSingleton().getByName("HeightmapSimulation");
|
||||||
|
|
||||||
|
mSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC);
|
||||||
|
|
||||||
|
mCamera = mSceneMgr->createCamera("RippleCamera");
|
||||||
|
|
||||||
|
mRectangle = new Ogre::Rectangle2D(true);
|
||||||
|
mRectangle->setBoundingBox(aabInf);
|
||||||
|
mRectangle->setCorners(-1.0, 1.0, 1.0, -1.0, false);
|
||||||
|
Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
|
||||||
|
node->attachObject(mRectangle);
|
||||||
|
|
||||||
|
mImpulse = new Ogre::Rectangle2D(true);
|
||||||
|
mImpulse->setCorners(-0.1, 0.1, 0.1, -0.1, false);
|
||||||
|
mImpulse->setBoundingBox(aabInf);
|
||||||
|
mImpulse->setMaterial("AddImpulse");
|
||||||
|
Ogre::SceneNode* impulseNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
|
||||||
|
impulseNode->attachObject(mImpulse);
|
||||||
|
|
||||||
|
float w=0.05;
|
||||||
|
for (int i=0; i<4; ++i)
|
||||||
|
{
|
||||||
|
Ogre::TexturePtr texture;
|
||||||
|
if (i != 3)
|
||||||
|
texture = Ogre::TextureManager::getSingleton().createManual("RippleHeight" + Ogre::StringConverter::toString(i),
|
||||||
|
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mTextureSize, mTextureSize, 1, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET);
|
||||||
|
else
|
||||||
|
texture = Ogre::TextureManager::getSingleton().createManual("RippleNormal",
|
||||||
|
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mTextureSize, mTextureSize, 1, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET);
|
||||||
|
|
||||||
|
|
||||||
|
Ogre::RenderTexture* rt = texture->getBuffer()->getRenderTarget();
|
||||||
|
rt->removeAllViewports();
|
||||||
|
rt->addViewport(mCamera);
|
||||||
|
rt->setAutoUpdated(false);
|
||||||
|
rt->getViewport(0)->setClearEveryFrame(false);
|
||||||
|
|
||||||
|
// debug overlay
|
||||||
|
Ogre::Rectangle2D* debugOverlay = new Ogre::Rectangle2D(true);
|
||||||
|
debugOverlay->setCorners(w*2-1, 0.9, (w+0.18)*2-1, 0.4, false);
|
||||||
|
w += 0.2;
|
||||||
|
debugOverlay->setBoundingBox(aabInf);
|
||||||
|
|
||||||
|
Ogre::SceneNode* debugNode = mMainSceneMgr->getRootSceneNode()->createChildSceneNode();
|
||||||
|
debugNode->attachObject(debugOverlay);
|
||||||
|
|
||||||
|
Ogre::MaterialPtr debugMaterial = Ogre::MaterialManager::getSingleton().create("RippleDebug" + Ogre::StringConverter::toString(i),
|
||||||
|
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
|
||||||
|
|
||||||
|
if (i != 3)
|
||||||
|
debugMaterial->getTechnique(0)->getPass(0)->createTextureUnitState("RippleHeight" + Ogre::StringConverter::toString(i));
|
||||||
|
else
|
||||||
|
debugMaterial->getTechnique(0)->getPass(0)->createTextureUnitState("RippleNormal");
|
||||||
|
debugMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false);
|
||||||
|
|
||||||
|
debugOverlay->setMaterial("RippleDebug" + Ogre::StringConverter::toString(i));
|
||||||
|
|
||||||
|
mRenderTargets[i] = rt;
|
||||||
|
mTextures[i] = texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
sh::Factory::getInstance().setSharedParameter("rippleTextureSize", sh::makeProperty<sh::Vector4>(
|
||||||
|
new sh::Vector4(1.0/512, 1.0/512, 512, 512)));
|
||||||
|
sh::Factory::getInstance().setSharedParameter("rippleCenter", sh::makeProperty<sh::Vector3>(
|
||||||
|
new sh::Vector3(0, 0, 0)));
|
||||||
|
sh::Factory::getInstance().setSharedParameter("rippleAreaLength", sh::makeProperty<sh::FloatValue>(
|
||||||
|
new sh::FloatValue(mRippleAreaLength)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
RippleSimulation::~RippleSimulation()
|
||||||
|
{
|
||||||
|
delete mRectangle;
|
||||||
|
|
||||||
|
Ogre::Root::getSingleton().destroySceneManager(mSceneMgr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RippleSimulation::update(float dt, Ogre::Vector2 position)
|
||||||
|
{
|
||||||
|
// try to keep 20 fps
|
||||||
|
mTime += dt;
|
||||||
|
|
||||||
|
while (mTime >= 1/20.0)
|
||||||
|
{
|
||||||
|
mPreviousFrameOffset = mCurrentFrameOffset;
|
||||||
|
|
||||||
|
mCurrentFrameOffset = position - mRippleCenter;
|
||||||
|
// add texel offsets from previous frame.
|
||||||
|
mCurrentFrameOffset += mTexelOffset;
|
||||||
|
|
||||||
|
mTexelOffset = Ogre::Vector2(std::fmod(mCurrentFrameOffset.x, 1.0f/mTextureSize),
|
||||||
|
std::fmod(mCurrentFrameOffset.y, 1.0f/mTextureSize));
|
||||||
|
|
||||||
|
// now subtract new offset in order to snap to texels
|
||||||
|
mCurrentFrameOffset -= mTexelOffset;
|
||||||
|
|
||||||
|
// texture coordinate space
|
||||||
|
mCurrentFrameOffset /= mRippleAreaLength;
|
||||||
|
|
||||||
|
std::cout << "Offset " << mCurrentFrameOffset << std::endl;
|
||||||
|
|
||||||
|
mRippleCenter = position;
|
||||||
|
|
||||||
|
addImpulses();
|
||||||
|
waterSimulation();
|
||||||
|
heightMapToNormalMap();
|
||||||
|
|
||||||
|
swapHeightMaps();
|
||||||
|
mTime -= 1/20.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sh::Factory::getInstance().setSharedParameter("rippleCenter", sh::makeProperty<sh::Vector3>(
|
||||||
|
new sh::Vector3(mRippleCenter.x + mTexelOffset.x, mRippleCenter.y + mTexelOffset.y, 0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RippleSimulation::addImpulse(Ogre::Vector2 position)
|
||||||
|
{
|
||||||
|
mImpulses.push(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RippleSimulation::addImpulses()
|
||||||
|
{
|
||||||
|
mRectangle->setVisible(false);
|
||||||
|
mImpulse->setVisible(true);
|
||||||
|
|
||||||
|
while (mImpulses.size())
|
||||||
|
{
|
||||||
|
Ogre::Vector2 pos = mImpulses.front();
|
||||||
|
pos -= mRippleCenter;
|
||||||
|
pos /= mRippleAreaLength;
|
||||||
|
float size = mImpulseSize / mRippleAreaLength;
|
||||||
|
mImpulse->setCorners(pos.x-size, pos.y+size, pos.x+size, pos.y-size, false);
|
||||||
|
mImpulses.pop();
|
||||||
|
|
||||||
|
mRenderTargets[1]->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
mImpulse->setVisible(false);
|
||||||
|
mRectangle->setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RippleSimulation::waterSimulation()
|
||||||
|
{
|
||||||
|
mRectangle->setMaterial("HeightmapSimulation");
|
||||||
|
|
||||||
|
sh::Factory::getInstance().setTextureAlias("Heightmap0", mTextures[0]->getName());
|
||||||
|
sh::Factory::getInstance().setTextureAlias("Heightmap1", mTextures[1]->getName());
|
||||||
|
|
||||||
|
sh::Factory::getInstance().setSharedParameter("currentFrameOffset", sh::makeProperty<sh::Vector3>(
|
||||||
|
new sh::Vector3(mCurrentFrameOffset.x, mCurrentFrameOffset.y, 0)));
|
||||||
|
sh::Factory::getInstance().setSharedParameter("previousFrameOffset", sh::makeProperty<sh::Vector3>(
|
||||||
|
new sh::Vector3(mPreviousFrameOffset.x, mPreviousFrameOffset.y, 0)));
|
||||||
|
|
||||||
|
mRenderTargets[2]->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RippleSimulation::heightMapToNormalMap()
|
||||||
|
{
|
||||||
|
mRectangle->setMaterial("HeightToNormalMap");
|
||||||
|
|
||||||
|
sh::Factory::getInstance().setTextureAlias("Heightmap2", mTextures[2]->getName());
|
||||||
|
|
||||||
|
mRenderTargets[TEX_NORMAL]->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RippleSimulation::swapHeightMaps()
|
||||||
|
{
|
||||||
|
// 0 -> 1 -> 2 to 2 -> 0 ->1
|
||||||
|
Ogre::RenderTexture* tmp = mRenderTargets[0];
|
||||||
|
Ogre::TexturePtr tmp2 = mTextures[0];
|
||||||
|
|
||||||
|
mRenderTargets[0] = mRenderTargets[1];
|
||||||
|
mTextures[0] = mTextures[1];
|
||||||
|
|
||||||
|
mRenderTargets[1] = mRenderTargets[2];
|
||||||
|
mTextures[1] = mTextures[2];
|
||||||
|
|
||||||
|
mRenderTargets[2] = tmp;
|
||||||
|
mTextures[2] = tmp2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
#ifndef RIPPLE_SIMULATION_H
|
||||||
|
#define RIPPLE_SIMULATION_H
|
||||||
|
|
||||||
|
#include <OgreTexture.h>
|
||||||
|
#include <OgreMaterial.h>
|
||||||
|
#include <OgreVector2.h>
|
||||||
|
|
||||||
|
namespace Ogre
|
||||||
|
{
|
||||||
|
class RenderTexture;
|
||||||
|
class Camera;
|
||||||
|
class SceneManager;
|
||||||
|
class Rectangle2D;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace MWRender
|
||||||
|
{
|
||||||
|
|
||||||
|
class RippleSimulation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RippleSimulation(Ogre::SceneManager* mainSceneManager);
|
||||||
|
~RippleSimulation();
|
||||||
|
|
||||||
|
void update(float dt, Ogre::Vector2 position);
|
||||||
|
|
||||||
|
void addImpulse (Ogre::Vector2 position);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ogre::RenderTexture* mRenderTargets[4];
|
||||||
|
Ogre::TexturePtr mTextures[4];
|
||||||
|
|
||||||
|
int mTextureSize;
|
||||||
|
float mRippleAreaLength;
|
||||||
|
float mImpulseSize;
|
||||||
|
|
||||||
|
Ogre::Camera* mCamera;
|
||||||
|
|
||||||
|
// own scenemanager to render our simulation
|
||||||
|
Ogre::SceneManager* mSceneMgr;
|
||||||
|
Ogre::Rectangle2D* mRectangle;
|
||||||
|
|
||||||
|
// scenemanager to create the debug overlays on
|
||||||
|
Ogre::SceneManager* mMainSceneMgr;
|
||||||
|
|
||||||
|
Ogre::MaterialPtr mHeightmapMaterial;
|
||||||
|
Ogre::MaterialPtr mHeightToNormalMapMaterial;
|
||||||
|
|
||||||
|
static const int TEX_NORMAL = 3;
|
||||||
|
|
||||||
|
Ogre::Rectangle2D* mImpulse;
|
||||||
|
|
||||||
|
std::queue <Ogre::Vector2> mImpulses;
|
||||||
|
|
||||||
|
void addImpulses();
|
||||||
|
void heightMapToNormalMap();
|
||||||
|
void waterSimulation();
|
||||||
|
void swapHeightMaps();
|
||||||
|
|
||||||
|
float mTime;
|
||||||
|
|
||||||
|
Ogre::Vector2 mRippleCenter;
|
||||||
|
|
||||||
|
Ogre::Vector2 mTexelOffset;
|
||||||
|
|
||||||
|
Ogre::Vector2 mCurrentFrameOffset;
|
||||||
|
Ogre::Vector2 mPreviousFrameOffset;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,59 @@
|
|||||||
|
material HeightmapSimulation
|
||||||
|
{
|
||||||
|
allow_fixed_function false
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
depth_check off
|
||||||
|
depth_write off
|
||||||
|
vertex_program transform_vertex
|
||||||
|
fragment_program watersim_fragment
|
||||||
|
|
||||||
|
texture_unit heightPrevSampler
|
||||||
|
{
|
||||||
|
tex_address_mode border
|
||||||
|
tex_border_colour 0 0 0
|
||||||
|
texture_alias Heightmap0
|
||||||
|
}
|
||||||
|
texture_unit heightCurrentSampler
|
||||||
|
{
|
||||||
|
tex_address_mode border
|
||||||
|
tex_border_colour 0 0 0
|
||||||
|
texture_alias Heightmap1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
material HeightToNormalMap
|
||||||
|
{
|
||||||
|
allow_fixed_function false
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
depth_check off
|
||||||
|
depth_write off
|
||||||
|
vertex_program transform_vertex
|
||||||
|
fragment_program height_to_normal_fragment
|
||||||
|
|
||||||
|
texture_unit heightCurrentSampler
|
||||||
|
{
|
||||||
|
texture_alias Heightmap2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
material AddImpulse
|
||||||
|
{
|
||||||
|
allow_fixed_function false
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
depth_check off
|
||||||
|
depth_write off
|
||||||
|
scene_blend alpha_blend
|
||||||
|
vertex_program transform_vertex
|
||||||
|
fragment_program add_impulse_fragment
|
||||||
|
|
||||||
|
texture_unit alphaMap
|
||||||
|
{
|
||||||
|
texture circle.png
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
shader_set transform_vertex
|
||||||
|
{
|
||||||
|
source quad.shader
|
||||||
|
type vertex
|
||||||
|
profiles_cg vs_2_0 vp40 arbvp1
|
||||||
|
profiles_hlsl vs_2_0
|
||||||
|
}
|
||||||
|
|
||||||
|
shader_set watersim_fragment
|
||||||
|
{
|
||||||
|
source watersim_heightmap.shader
|
||||||
|
type fragment
|
||||||
|
profiles_cg ps_3_0 ps_2_x ps_2_0 fp40 arbfp1
|
||||||
|
profiles_hlsl ps_3_0 ps_2_0
|
||||||
|
}
|
||||||
|
|
||||||
|
shader_set height_to_normal_fragment
|
||||||
|
{
|
||||||
|
source watersim_heighttonormal.shader
|
||||||
|
type fragment
|
||||||
|
profiles_cg ps_3_0 ps_2_x ps_2_0 fp40 arbfp1
|
||||||
|
profiles_hlsl ps_3_0 ps_2_0
|
||||||
|
}
|
||||||
|
|
||||||
|
shader_set add_impulse_fragment
|
||||||
|
{
|
||||||
|
source watersim_addimpulse.shader
|
||||||
|
type fragment
|
||||||
|
profiles_cg ps_3_0 ps_2_x ps_2_0 fp40 arbfp1
|
||||||
|
profiles_hlsl ps_3_0 ps_2_0
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
#include "core.h"
|
||||||
|
#include "watersim_common.h"
|
||||||
|
|
||||||
|
SH_BEGIN_PROGRAM
|
||||||
|
shInput(float2, UV)
|
||||||
|
shSampler2D(alphaMap)
|
||||||
|
|
||||||
|
SH_START_PROGRAM
|
||||||
|
{
|
||||||
|
shOutputColour(0) = EncodeHeightmap(1.0);
|
||||||
|
shOutputColour(0).a = shSample (alphaMap, UV.xy).a;
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
float DecodeHeightmap(float4 heightmap)
|
||||||
|
{
|
||||||
|
float4 table = float4(1.0, -1.0, 0.0, 0.0);
|
||||||
|
return dot(heightmap, table);
|
||||||
|
}
|
||||||
|
|
||||||
|
float DecodeHeightmap(shTexture2D HeightmapSampler, float2 texcoord)
|
||||||
|
{
|
||||||
|
float4 heightmap = shSample(HeightmapSampler, texcoord);
|
||||||
|
return DecodeHeightmap(heightmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 EncodeHeightmap(float fHeight)
|
||||||
|
{
|
||||||
|
float h = fHeight;
|
||||||
|
float positive = fHeight > 0.0 ? fHeight : 0.0;
|
||||||
|
float negative = fHeight < 0.0 ? -fHeight : 0.0;
|
||||||
|
|
||||||
|
float4 color = float4(0,0,0,0);
|
||||||
|
|
||||||
|
color.r = positive;
|
||||||
|
color.g = negative;
|
||||||
|
|
||||||
|
return color;
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
#define DAMPING 0.92
|
||||||
|
|
||||||
|
#include "watersim_common.h"
|
||||||
|
|
||||||
|
SH_BEGIN_PROGRAM
|
||||||
|
shInput(float2, UV)
|
||||||
|
shSampler2D(heightPrevSampler)
|
||||||
|
shSampler2D(heightCurrentSampler)
|
||||||
|
shUniform(float3, previousFrameOffset) @shSharedParameter(previousFrameOffset, previousFrameOffset)
|
||||||
|
shUniform(float3, currentFrameOffset) @shSharedParameter(currentFrameOffset, currentFrameOffset)
|
||||||
|
shUniform(float4, rippleTextureSize) @shSharedParameter(rippleTextureSize, rippleTextureSize)
|
||||||
|
|
||||||
|
SH_START_PROGRAM
|
||||||
|
{
|
||||||
|
const float3 offset[4] = float3[4](
|
||||||
|
float3(-1.0, 0.0, 0.25),
|
||||||
|
float3( 1.0, 0.0, 0.25),
|
||||||
|
float3( 0.0,-1.0, 0.25),
|
||||||
|
float3( 0.0, 1.0, 0.25)
|
||||||
|
);
|
||||||
|
|
||||||
|
float fHeightPrev = DecodeHeightmap(heightPrevSampler, UV.xy + previousFrameOffset.xy + currentFrameOffset.xy);
|
||||||
|
|
||||||
|
float fNeighCurrent = 0;
|
||||||
|
for ( int i=0; i<4; i++ )
|
||||||
|
{
|
||||||
|
float2 vTexcoord = UV + currentFrameOffset.xy + offset[i].xy * rippleTextureSize.xy;
|
||||||
|
fNeighCurrent += (DecodeHeightmap(heightCurrentSampler, vTexcoord) * offset[i].z);
|
||||||
|
}
|
||||||
|
|
||||||
|
float fHeight = fNeighCurrent * 2.0 - fHeightPrev;
|
||||||
|
|
||||||
|
fHeight *= DAMPING;
|
||||||
|
|
||||||
|
shOutputColour(0) = EncodeHeightmap(fHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
|||||||
|
#include "core.h"
|
||||||
|
#include "watersim_common.h"
|
||||||
|
|
||||||
|
SH_BEGIN_PROGRAM
|
||||||
|
shInput(float2, UV)
|
||||||
|
shSampler2D(heightCurrentSampler)
|
||||||
|
shUniform(float4, rippleTextureSize) @shSharedParameter(rippleTextureSize, rippleTextureSize)
|
||||||
|
|
||||||
|
SH_START_PROGRAM
|
||||||
|
{
|
||||||
|
float2 offset[4] = float2[4] (
|
||||||
|
vec2(-1.0, 0.0),
|
||||||
|
vec2( 1.0, 0.0),
|
||||||
|
vec2( 0.0,-1.0),
|
||||||
|
vec2( 0.0, 1.0)
|
||||||
|
);
|
||||||
|
|
||||||
|
float fHeightL = DecodeHeightmap(heightCurrentSampler, UV.xy + offset[0]*rippleTextureSize.xy);
|
||||||
|
float fHeightR = DecodeHeightmap(heightCurrentSampler, UV.xy + offset[1]*rippleTextureSize.xy);
|
||||||
|
float fHeightT = DecodeHeightmap(heightCurrentSampler, UV.xy + offset[2]*rippleTextureSize.xy);
|
||||||
|
float fHeightB = DecodeHeightmap(heightCurrentSampler, UV.xy + offset[3]*rippleTextureSize.xy);
|
||||||
|
|
||||||
|
float3 n = float3(fHeightB - fHeightT, fHeightR - fHeightL, 1.0);
|
||||||
|
float3 normal = (n + 1.0) * 0.5;
|
||||||
|
|
||||||
|
shOutputColour(0) = float4(normal.rgb, 1.0);
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 753 B |
Loading…
Reference in New Issue